summaryrefslogtreecommitdiff
path: root/include/mysql/service_debug_sync.h
blob: 0bd49a13458baf4c1eb9bea27554c0a478797bab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#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-1335  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