123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- #ifndef MYSQL_SERVICE_DEBUG_SYNC_INCLUDED
- /* Copyright (c) 2009, 2010, Oracle and/or its affiliates.
- Copyright (c) 2012, Monty Program Ab
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; version 2 of the License.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
- /**
- @file
- == Debug Sync Facility ==
- The Debug Sync Facility allows placement of synchronization points in
- the server code by using the DEBUG_SYNC macro:
- open_tables(...)
- DEBUG_SYNC(thd, "after_open_tables");
- lock_tables(...)
- When activated, a sync point can
- - Emit a signal and/or
- - Wait for a signal
- Nomenclature:
- - signal: A value of a global variable that persists
- until overwritten by a new signal. The global
- variable can also be seen as a "signal post"
- or "flag mast". Then the signal is what is
- attached to the "signal post" or "flag mast".
- - emit a signal: Assign the value (the signal) to the global
- variable ("set a flag") and broadcast a
- global condition to wake those waiting for
- a signal.
- - wait for a signal: Loop over waiting for the global condition until
- the global value matches the wait-for signal.
- By default, all sync points are inactive. They do nothing (except to
- burn a couple of CPU cycles for checking if they are active).
- A sync point becomes active when an action is requested for it.
- To do so, put a line like this in the test case file:
- SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
- This activates the sync point 'after_open_tables'. It requests it to
- emit the signal 'opened' and wait for another thread to emit the signal
- 'flushed' when the thread's execution runs through the sync point.
- For every sync point there can be one action per thread only. Every
- thread can request multiple actions, but only one per sync point. In
- other words, a thread can activate multiple sync points.
- Here is an example how to activate and use the sync points:
- --connection conn1
- SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
- send INSERT INTO t1 VALUES(1);
- --connection conn2
- SET DEBUG_SYNC= 'now WAIT_FOR opened';
- SET DEBUG_SYNC= 'after_abort_locks SIGNAL flushed';
- FLUSH TABLE t1;
- When conn1 runs through the INSERT statement, it hits the sync point
- 'after_open_tables'. It notices that it is active and executes its
- action. It emits the signal 'opened' and waits for another thread to
- emit the signal 'flushed'.
- conn2 waits immediately at the special sync point 'now' for another
- thread to emit the 'opened' signal.
- A signal remains in effect until it is overwritten. If conn1 signals
- 'opened' before conn2 reaches 'now', conn2 will still find the 'opened'
- signal. It does not wait in this case.
- When conn2 reaches 'after_abort_locks', it signals 'flushed', which lets
- conn1 awake.
- Normally the activation of a sync point is cleared when it has been
- executed. Sometimes it is necessary to keep the sync point active for
- another execution. You can add an execute count to the action:
- SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 3';
- This sets the signal point's activation counter to 3. Each execution
- decrements the counter. After the third execution the sync point
- becomes inactive.
- One of the primary goals of this facility is to eliminate sleeps from
- the test suite. In most cases it should be possible to rewrite test
- cases so that they do not need to sleep. (But this facility cannot
- synchronize multiple processes.) However, to support test development,
- and as a last resort, sync point waiting times out. There is a default
- timeout, but it can be overridden:
- SET DEBUG_SYNC= 'name WAIT_FOR sig TIMEOUT 10 EXECUTE 2';
- TIMEOUT 0 is special: If the signal is not present, the wait times out
- immediately.
- When a wait timed out (even on TIMEOUT 0), a warning is generated so
- that it shows up in the test result.
- You can throw an error message and kill the query when a synchronization
- point is hit a certain number of times:
- SET DEBUG_SYNC= 'name HIT_LIMIT 3';
- Or combine it with signal and/or wait:
- SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 2 HIT_LIMIT 3';
- Here the first two hits emit the signal, the third hit returns the error
- message and kills the query.
- For cases where you are not sure that an action is taken and thus
- cleared in any case, you can force to clear (deactivate) a sync point:
- SET DEBUG_SYNC= 'name CLEAR';
- If you want to clear all actions and clear the global signal, use:
- SET DEBUG_SYNC= 'RESET';
- This is the only way to reset the global signal to an empty string.
- For testing of the facility itself you can execute a sync point just
- as if it had been hit:
- SET DEBUG_SYNC= 'name TEST';
- === Formal Syntax ===
- The string to "assign" to the DEBUG_SYNC variable can contain:
- {RESET |
- <sync point name> TEST |
- <sync point name> CLEAR |
- <sync point name> {{SIGNAL <signal name> |
- WAIT_FOR <signal name> [TIMEOUT <seconds>]}
- [EXECUTE <count>] &| HIT_LIMIT <count>}
- Here '&|' means 'and/or'. This means that one of the sections
- separated by '&|' must be present or both of them.
- === Activation/Deactivation ===
- The facility is an optional part of the MySQL server.
- It is enabled in a debug server by default.
- ./configure --enable-debug-sync
- The Debug Sync Facility, when compiled in, is disabled by default. It
- can be enabled by a mysqld command line option:
- --debug-sync-timeout[=default_wait_timeout_value_in_seconds]
- 'default_wait_timeout_value_in_seconds' is the default timeout for the
- WAIT_FOR action. If set to zero, the facility stays disabled.
- The facility is enabled by default in the test suite, but can be
- disabled with:
- mysql-test-run.pl ... --debug-sync-timeout=0 ...
- Likewise the default wait timeout can be set:
- mysql-test-run.pl ... --debug-sync-timeout=10 ...
- The command line option influences the readable value of the system
- variable 'debug_sync'.
- * If the facility is not compiled in, the system variable does not exist.
- * If --debug-sync-timeout=0 the value of the variable reads as "OFF".
- * Otherwise the value reads as "ON - current signal: " followed by the
- current signal string, which can be empty.
- The readable variable value is the same, regardless if read as global
- or session value.
- Setting the 'debug-sync' system variable requires 'SUPER' privilege.
- You can never read back the string that you assigned to the variable,
- unless you assign the value that the variable does already have. But
- that would give a parse error. A syntactically correct string is
- parsed into a debug sync action and stored apart from the variable value.
- === Implementation ===
- Pseudo code for a sync point:
- #define DEBUG_SYNC(thd, sync_point_name)
- if (unlikely(opt_debug_sync_timeout))
- debug_sync(thd, STRING_WITH_LEN(sync_point_name))
- The sync point performs a binary search in a sorted array of actions
- for this thread.
- The SET DEBUG_SYNC statement adds a requested action to the array or
- overwrites an existing action for the same sync point. When it adds a
- new action, the array is sorted again.
- === A typical synchronization pattern ===
- There are quite a few places in MySQL, where we use a synchronization
- pattern like this:
- mysql_mutex_lock(&mutex);
- thd->enter_cond(&condition_variable, &mutex, new_message);
- #if defined(ENABLE_DEBUG_SYNC)
- if (!thd->killed && !end_of_wait_condition)
- DEBUG_SYNC(thd, "sync_point_name");
- #endif
- while (!thd->killed && !end_of_wait_condition)
- mysql_cond_wait(&condition_variable, &mutex);
- thd->exit_cond(old_message);
- Here some explanations:
- thd->enter_cond() is used to register the condition variable and the
- mutex in thd->mysys_var. This is done to allow the thread to be
- interrupted (killed) from its sleep. Another thread can find the
- condition variable to signal and mutex to use for synchronization in
- this thread's THD::mysys_var.
- thd->enter_cond() requires the mutex to be acquired in advance.
- thd->exit_cond() unregisters the condition variable and mutex and
- releases the mutex.
- If you want to have a Debug Sync point with the wait, please place it
- behind enter_cond(). Only then you can safely decide, if the wait will
- be taken. Also you will have THD::proc_info correct when the sync
- point emits a signal. DEBUG_SYNC sets its own proc_info, but restores
- the previous one before releasing its internal mutex. As soon as
- another thread sees the signal, it does also see the proc_info from
- before entering the sync point. In this case it will be "new_message",
- which is associated with the wait that is to be synchronized.
- In the example above, the wait condition is repeated before the sync
- point. This is done to skip the sync point, if no wait takes place.
- The sync point is before the loop (not inside the loop) to have it hit
- once only. It is possible that the condition variable is signaled
- multiple times without the wait condition to be true.
- A bit off-topic: At some places, the loop is taken around the whole
- synchronization pattern:
- while (!thd->killed && !end_of_wait_condition)
- {
- mysql_mutex_lock(&mutex);
- thd->enter_cond(&condition_variable, &mutex, new_message);
- if (!thd->killed [&& !end_of_wait_condition])
- {
- [DEBUG_SYNC(thd, "sync_point_name");]
- mysql_cond_wait(&condition_variable, &mutex);
- }
- thd->exit_cond(old_message);
- }
- Note that it is important to repeat the test for thd->killed after
- enter_cond(). Otherwise the killing thread may kill this thread after
- it tested thd->killed in the loop condition and before it registered
- the condition variable and mutex in enter_cond(). In this case, the
- killing thread does not know that this thread is going to wait on a
- condition variable. It would just set THD::killed. But if we would not
- test it again, we would go asleep though we are killed. If the killing
- thread would kill us when we are after the second test, but still
- before sleeping, we hold the mutex, which is registered in mysys_var.
- The killing thread would try to acquire the mutex before signaling
- the condition variable. Since the mutex is only released implicitly in
- mysql_cond_wait(), the signaling happens at the right place. We
- have a safe synchronization.
- === Co-work with the DBUG facility ===
- When running the MySQL test suite with the --debug-dbug command line
- option, the Debug Sync Facility writes trace messages to the DBUG
- trace. The following shell commands proved very useful in extracting
- relevant information:
- egrep 'query:|debug_sync_exec:' mysql-test/var/log/mysqld.1.trace
- It shows all executed SQL statements and all actions executed by
- synchronization points.
- Sometimes it is also useful to see, which synchronization points have
- been run through (hit) with or without executing actions. Then add
- "|debug_sync_point:" to the egrep pattern.
- === Further reading ===
- For a discussion of other methods to synchronize threads see
- http://forge.mysql.com/wiki/MySQL_Internals_Test_Synchronization
- For complete syntax tests, functional tests, and examples see the test
- case debug_sync.test.
- See also http://forge.mysql.com/worklog/task.php?id=4259
- */
- #ifndef MYSQL_ABI_CHECK
- #include <stdlib.h>
- #endif
- #ifdef __cplusplus
- extern "C" {
- #endif
- #ifdef MYSQL_DYNAMIC_PLUGIN
- extern void (*debug_sync_service)(MYSQL_THD, const char *, size_t);
- #else
- #define debug_sync_service debug_sync_C_callback_ptr
- extern void (*debug_sync_C_callback_ptr)(MYSQL_THD, const char *, size_t);
- #endif
- #ifdef ENABLED_DEBUG_SYNC
- #define DEBUG_SYNC(thd, name) \
- do { \
- if (debug_sync_service) \
- debug_sync_service(thd, STRING_WITH_LEN(name)); \
- } while(0)
- #define DEBUG_SYNC_C_IF_THD(thd, name) \
- do { \
- if (debug_sync_service && thd) \
- debug_sync_service((MYSQL_THD) thd, STRING_WITH_LEN(name)); \
- } while(0)
- #else
- #define DEBUG_SYNC(thd,name) do { } while(0)
- #define DEBUG_SYNC_C_IF_THD(thd, _sync_point_name_) do { } while(0)
- #endif /* defined(ENABLED_DEBUG_SYNC) */
- /* compatibility macro */
- #define DEBUG_SYNC_C(name) DEBUG_SYNC(NULL, name)
- #ifdef __cplusplus
- }
- #endif
- #define MYSQL_SERVICE_DEBUG_SYNC_INCLUDED
- #endif
|