1 /* Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software Foundation,
21    51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
22 
23 /**
24   == Debug Sync Facility ==
25 
26   The Debug Sync Facility allows placement of synchronization points in
27   the server code by using the DEBUG_SYNC macro:
28 
29       open_tables(...)
30 
31       DEBUG_SYNC(thd, "after_open_tables");
32 
33       lock_tables(...)
34 
35   When activated, a sync point can
36 
37     - Emit a signal and/or
38     - Wait for a signal
39 
40   Nomenclature:
41 
42     - signal:             An event identified by a name that a signal
43                           thread uses to notify the wait thread that
44                           waits on this event. When the signal  thread
45                           notifies the wait thread, the signal name
46                           is copied into global list and the wait thread
47                           is signalled to wake up and proceed with further
48                           processing.
49 
50     - emit a signal:      Signal thread wakes up wait thread or multiple
51                           wait threads that shall wait for the signal identified
52                           by a signal name. This signal thread copies the signal
53                           name into a global list and broadcasts the event which
54                           wakes the threads that wait for this event.
55 
56     - wait for a signal:  Wait on a event indentified by the signal name until
57                           the signal thread signals the event.
58 
59   By default, all sync points are inactive. They do nothing (except to
60   burn a couple of CPU cycles for checking if they are active).
61 
62   A sync point becomes active when an action is requested for it.
63   To do so, put a line like this in the test case file:
64 
65       SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
66 
67   This activates the sync point 'after_open_tables'. It requests it to
68   emit the signal 'opened' and wait for another thread to emit the signal
69   'flushed' when the thread's execution runs through the sync point.
70 
71   For every sync point there can be one action per thread only. Every
72   thread can request multiple actions, but only one per sync point. In
73   other words, a thread can activate multiple sync points.
74 
75   Here is an example how to activate and use the sync points:
76 
77       --connection conn1
78       SET DEBUG_SYNC= 'after_open_tables SIGNAL opened WAIT_FOR flushed';
79       send INSERT INTO t1 VALUES(1);
80           --connection conn2
81           SET DEBUG_SYNC= 'now WAIT_FOR opened';
82           SET DEBUG_SYNC= 'after_abort_locks SIGNAL flushed';
83           FLUSH TABLE t1;
84 
85   When conn1 runs through the INSERT statement, it hits the sync point
86   'after_open_tables'. It notices that it is active and executes its
87   action. It emits the signal 'opened' and waits for another thread to
88   emit the signal 'flushed'.
89 
90   conn2 waits immediately at the special sync point 'now' for another
91   thread to emit the 'opened' signal.
92 
93   If conn1 signals 'opened' before conn2 reaches 'now', conn2 will find
94   the 'opened' signal. The wait thread shall not wait in this case.
95 
96   When conn2 reaches 'after_abort_locks', it signals 'flushed', which lets
97   conn1 awake and clears the 'flushed' signal from the global list. In case
98   the 'flushed' signal is to be notified to multiple wait threads, an attribute
99   NO_CLEAR_EVENT need to be specified with the WAIT_FOR in addition to signal
100   the name as:
101       SET DEBUG_SYNC= 'WAIT_FOR flushed NO_CLEAR_EVENT';
102   It is up to the user to ensure once when all the wait threads have processed
103   the 'flushed' signal to clear/deactivate the signal using the RESET action
104   of DEBUG_SYNC accordingly.
105 
106 
107   Normally the activation of a sync point is cleared when it has been
108   executed. Sometimes it is necessary to keep the sync point active for
109   another execution. You can add an execute count to the action:
110 
111       SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 3';
112 
113   This sets the signal point's activation counter to 3. Each execution
114   decrements the counter. After the third execution the sync point
115   becomes inactive.
116 
117   One of the primary goals of this facility is to eliminate sleeps from
118   the test suite. In most cases it should be possible to rewrite test
119   cases so that they do not need to sleep. (But this facility cannot
120   synchronize multiple processes.) However, to support test development,
121   and as a last resort, sync point waiting times out. There is a default
122   timeout, but it can be overridden:
123 
124       SET DEBUG_SYNC= 'name WAIT_FOR sig TIMEOUT 10 EXECUTE 2';
125 
126   TIMEOUT 0 is special: If the signal is not present, the wait times out
127   immediately.
128 
129   When a wait timed out (even on TIMEOUT 0), a warning is generated so
130   that it shows up in the test result.
131 
132   You can throw an error message and kill the query when a synchronization
133   point is hit a certain number of times:
134 
135       SET DEBUG_SYNC= 'name HIT_LIMIT 3';
136 
137   Or combine it with signal and/or wait:
138 
139       SET DEBUG_SYNC= 'name SIGNAL sig EXECUTE 2 HIT_LIMIT 3';
140 
141   Here the first two hits emit the signal, the third hit returns the error
142   message and kills the query.
143 
144   For cases where you are not sure that an action is taken and thus
145   cleared in any case, you can force to clear (deactivate) a sync point:
146 
147       SET DEBUG_SYNC= 'name CLEAR';
148 
149   If you want to clear all actions and clear the global signal, use:
150 
151       SET DEBUG_SYNC= 'RESET';
152 
153   This is the only way to reset the global signal to an empty string.
154 
155   For testing of the facility itself you can execute a sync point just
156   as if it had been hit:
157 
158       SET DEBUG_SYNC= 'name TEST';
159 
160 
161   === Formal Syntax ===
162 
163   The string to "assign" to the DEBUG_SYNC variable can contain:
164 
165       {RESET |
166        <sync point name> TEST |
167        <sync point name> CLEAR |
168        <sync point name> {{SIGNAL <signal name> |
169                            WAIT_FOR <signal name> [TIMEOUT <seconds>]
170                            [NO_CLEAR_EVENT]}
171                           [EXECUTE <count>] &| HIT_LIMIT <count>}
172 
173   Here '&|' means 'and/or'. This means that one of the sections
174   separated by '&|' must be present or both of them.
175 
176 
177   === Activation/Deactivation ===
178 
179   The facility is an optional part of the MySQL server.
180   It is enabled in a debug server by default.
181 
182   The Debug Sync Facility, when compiled in, is disabled by default. It
183   can be enabled by a mysqld command line option:
184 
185       --debug-sync-timeout[=default_wait_timeout_value_in_seconds]
186 
187   'default_wait_timeout_value_in_seconds' is the default timeout for the
188   WAIT_FOR action. If set to zero, the facility stays disabled.
189 
190   The facility is enabled by default in the test suite, but can be
191   disabled with:
192 
193       mysql-test-run.pl ... --debug-sync-timeout=0 ...
194 
195   Likewise the default wait timeout can be set:
196 
197       mysql-test-run.pl ... --debug-sync-timeout=10 ...
198 
199   The command line option influences the readable value of the system
200   variable 'debug_sync'.
201 
202   * If the facility is not compiled in, the system variable does not exist.
203 
204   * If --debug-sync-timeout=0 the value of the variable reads as "OFF".
205 
206   * Otherwise the value reads as "ON - current signal: " followed by the
207     current signal string, which can be empty.
208 
209   The readable variable value is the same, regardless if read as global
210   or session value.
211 
212   Setting the 'debug-sync' system variable requires 'SUPER' privilege.
213   You can never read back the string that you assigned to the variable,
214   unless you assign the value that the variable does already have. But
215   that would give a parse error. A syntactically correct string is
216   parsed into a debug sync action and stored apart from the variable value.
217 
218 
219   === Implementation ===
220 
221   Pseudo code for a sync point:
222 
223       #define DEBUG_SYNC(thd, sync_point_name)
224                 if (unlikely(opt_debug_sync_timeout))
225                   debug_sync(thd, STRING_WITH_LEN(sync_point_name))
226 
227   The sync point performs a binary search in a sorted array of actions
228   for this thread.
229 
230   The SET DEBUG_SYNC statement adds a requested action to the array or
231   overwrites an existing action for the same sync point. When it adds a
232   new action, the array is sorted again.
233 
234 
235   === A typical synchronization pattern ===
236 
237   There are quite a few places in MySQL, where we use a synchronization
238   pattern like this:
239 
240   mysql_mutex_lock(&mutex);
241   thd->enter_cond(&condition_variable, &mutex, new_message);
242   #if defined(ENABLE_DEBUG_SYNC)
243   if (!thd->killed && !end_of_wait_condition)
244      DEBUG_SYNC(thd, "sync_point_name");
245   #endif
246   while (!thd->killed && !end_of_wait_condition)
247     mysql_cond_wait(&condition_variable, &mutex);
248   thd->exit_cond(old_message);
249 
250   Here some explanations:
251 
252   thd->enter_cond() is used to register the condition variable and the
253   mutex in thd->mysys_var. This is done to allow the thread to be
254   interrupted (killed) from its sleep. Another thread can find the
255   condition variable to signal and mutex to use for synchronization in
256   this thread's THD::mysys_var.
257 
258   thd->enter_cond() requires the mutex to be acquired in advance.
259 
260   thd->exit_cond() unregisters the condition variable and mutex and
261   releases the mutex.
262 
263   If you want to have a Debug Sync point with the wait, please place it
264   behind enter_cond(). Only then you can safely decide, if the wait will
265   be taken. Also you will have THD::proc_info correct when the sync
266   point emits a signal. DEBUG_SYNC sets its own proc_info, but restores
267   the previous one before releasing its internal mutex. As soon as
268   another thread sees the signal, it does also see the proc_info from
269   before entering the sync point. In this case it will be "new_message",
270   which is associated with the wait that is to be synchronized.
271 
272   In the example above, the wait condition is repeated before the sync
273   point. This is done to skip the sync point, if no wait takes place.
274   The sync point is before the loop (not inside the loop) to have it hit
275   once only. It is possible that the condition variable is signaled
276   multiple times without the wait condition to be true.
277 
278   A bit off-topic: At some places, the loop is taken around the whole
279   synchronization pattern:
280 
281   while (!thd->killed && !end_of_wait_condition)
282   {
283     mysql_mutex_lock(&mutex);
284     thd->enter_cond(&condition_variable, &mutex, new_message);
285     if (!thd->killed [&& !end_of_wait_condition])
286     {
287       [DEBUG_SYNC(thd, "sync_point_name");]
288       mysql_cond_wait(&condition_variable, &mutex);
289     }
290     thd->exit_cond(old_message);
291   }
292 
293   Note that it is important to repeat the test for thd->killed after
294   enter_cond(). Otherwise the killing thread may kill this thread after
295   it tested thd->killed in the loop condition and before it registered
296   the condition variable and mutex in enter_cond(). In this case, the
297   killing thread does not know that this thread is going to wait on a
298   condition variable. It would just set THD::killed. But if we would not
299   test it again, we would go asleep though we are killed. If the killing
300   thread would kill us when we are after the second test, but still
301   before sleeping, we hold the mutex, which is registered in mysys_var.
302   The killing thread would try to acquire the mutex before signaling
303   the condition variable. Since the mutex is only released implicitly in
304   mysql_cond_wait(), the signaling happens at the right place. We
305   have a safe synchronization.
306 
307   === Co-work with the DBUG facility ===
308 
309   When running the MySQL test suite with the --debug command line
310   option, the Debug Sync Facility writes trace messages to the DBUG
311   trace. The following shell commands proved very useful in extracting
312   relevant information:
313 
314   egrep 'query:|debug_sync_exec:' mysql-test/var/log/mysqld.1.trace
315 
316   It shows all executed SQL statements and all actions executed by
317   synchronization points.
318 
319   Sometimes it is also useful to see, which synchronization points have
320   been run through (hit) with or without executing actions. Then add
321   "|debug_sync_point:" to the egrep pattern.
322 
323   === Further reading ===
324 
325   For a discussion of other methods to synchronize threads see
326   http://forge.mysql.com/wiki/MySQL_Internals_Test_Synchronization
327 
328   For complete syntax tests, functional tests, and examples see the test
329   case debug_sync.test.
330 
331   See also worklog entry WL#4259 - Test Synchronization Facility
332 */
333 
334 #include "debug_sync.h"
335 
336 #if defined(ENABLED_DEBUG_SYNC)
337 
338 /*
339   Due to weaknesses in our include files, we need to include
340   sql_priv.h here. To have THD declared, we need to include
341   sql_class.h. This includes log_event.h, which in turn requires
342   declarations from sql_priv.h (e.g. OPTION_AUTO_IS_NULL).
343   sql_priv.h includes almost everything, so is sufficient here.
344 */
345 #include "sql_priv.h"
346 #include "sql_parse.h"
347 
348 #include <set>
349 #include <string>
350 
351 using std::max;
352 using std::min;
353 
354 /*
355   Action to perform at a synchronization point.
356   NOTE: This structure is moved around in memory by realloc(), qsort(),
357         and memmove(). Do not add objects with non-trivial constuctors
358         or destructors, which might prevent moving of this structure
359         with these functions.
360 */
361 struct st_debug_sync_action
362 {
363   ulong         activation_count;       /* max(hit_limit, execute) */
364   ulong         hit_limit;              /* hits before kill query */
365   ulong         execute;                /* executes before self-clear */
366   ulong         timeout;                /* wait_for timeout */
367   String        signal;                 /* signal to emit */
368   String        wait_for;               /* signal to wait for */
369   String        sync_point;             /* sync point name */
370   bool          need_sort;              /* if new action, array needs sort */
371   bool          clear_event;            /* do not clear signal if false */
372 };
373 
374 /* Debug sync control. Referenced by THD. */
375 struct st_debug_sync_control
376 {
377   st_debug_sync_action  *ds_action;             /* array of actions */
378   uint                  ds_active;              /* # active actions */
379   uint                  ds_allocated;           /* # allocated actions */
380   ulonglong             dsp_hits;               /* statistics */
381   ulonglong             dsp_executed;           /* statistics */
382   ulonglong             dsp_max_active;         /* statistics */
383   /*
384     thd->proc_info points at unsynchronized memory.
385     It must not go away as long as the thread exists.
386   */
387   char                  ds_proc_info[80];       /* proc_info string */
388 };
389 
390 typedef std::set<std::string> signal_event_set;
391 
392 /**
393   Definitions for the debug sync facility.
394   1. Global set of signal names which are signalled.
395   2. Global condition variable for signaling and waiting.
396   3. Global mutex to synchronize access to the above.
397 */
398 struct st_debug_sync_globals
399 {
400   signal_event_set      ds_signal_set;          /* list of signals signalled */
401   mysql_cond_t          ds_cond;                /* condition variable */
402   mysql_mutex_t         ds_mutex;               /* mutex variable */
403   ulonglong             dsp_hits;               /* statistics */
404   ulonglong             dsp_executed;           /* statistics */
405   ulonglong             dsp_max_active;         /* statistics */
406 
st_debug_sync_globalsst_debug_sync_globals407   st_debug_sync_globals() : dsp_hits(0), dsp_executed(0), dsp_max_active(0) {}
408 private:
409   // Not implemented:
410   st_debug_sync_globals(const st_debug_sync_globals&);
411   st_debug_sync_globals &operator=(const st_debug_sync_globals&);
412 };
413 static st_debug_sync_globals debug_sync_global; /* All globals in one object */
414 
415 /**
416   Callback pointer for C files.
417 */
418 extern "C" void (*debug_sync_C_callback_ptr)(const char *, size_t);
419 
420 /**
421   Callbacks from C files.
422 */
423 C_MODE_START
424 static void debug_sync_C_callback(const char *, size_t);
425 static int debug_sync_qsort_cmp(const void *, const void *);
426 C_MODE_END
427 
428 /**
429   Callback for debug sync, to be used by C files. See thr_lock.c for example.
430 
431   @description
432 
433     We cannot place a sync point directly in C files (like those in mysys or
434     certain storage engines written mostly in C like MyISAM or Maria). Because
435     they are C code and do not include sql_priv.h. So they do not know the
436     macro DEBUG_SYNC(thd, sync_point_name). The macro needs a 'thd' argument.
437     Hence it cannot be used in files outside of the sql/ directory.
438 
439     The workaround is to call back simple functions like this one from
440     non-sql/ files.
441 
442     We want to allow modules like thr_lock to be used without sql/ and
443     especially without Debug Sync. So we cannot just do a simple call
444     of the callback function. Instead we provide a global pointer in
445     the other file, which is to be set to the callback by Debug Sync.
446     If the pointer is not set, no call back will be done. If Debug
447     Sync sets the pointer to a callback function like this one, it will
448     be called. That way thr_lock.c does not have an undefined reference
449     to Debug Sync and can be used without it. Debug Sync, in contrast,
450     has an undefined reference to that pointer and thus requires
451     thr_lock to be linked too. But this is not a problem as it is part
452     of the MySQL server anyway.
453 
454   @note
455     The callback pointer in C files is set only if debug sync is
456     initialized. And this is done only if opt_debug_sync_timeout is set.
457 */
458 
debug_sync_C_callback(const char * sync_point_name,size_t name_len)459 static void debug_sync_C_callback(const char *sync_point_name,
460                                   size_t name_len)
461 {
462   if (unlikely(opt_debug_sync_timeout))
463     debug_sync(current_thd, sync_point_name, name_len);
464 }
465 
466 #ifdef HAVE_PSI_INTERFACE
467 static PSI_mutex_key key_debug_sync_globals_ds_mutex;
468 
469 static PSI_mutex_info all_debug_sync_mutexes[]=
470 {
471   { &key_debug_sync_globals_ds_mutex, "DEBUG_SYNC::mutex", PSI_FLAG_GLOBAL}
472 };
473 
474 static PSI_cond_key key_debug_sync_globals_ds_cond;
475 
476 static PSI_cond_info all_debug_sync_conds[]=
477 {
478   { &key_debug_sync_globals_ds_cond, "DEBUG_SYNC::cond", PSI_FLAG_GLOBAL}
479 };
480 
init_debug_sync_psi_keys(void)481 static void init_debug_sync_psi_keys(void)
482 {
483   const char* category= "sql";
484   int count;
485 
486   count= array_elements(all_debug_sync_mutexes);
487   mysql_mutex_register(category, all_debug_sync_mutexes, count);
488 
489   count= array_elements(all_debug_sync_conds);
490   mysql_cond_register(category, all_debug_sync_conds, count);
491 }
492 #endif /* HAVE_PSI_INTERFACE */
493 
494 
495 /**
496   Initialize the debug sync facility at server start.
497 
498   @return status
499     @retval     0       ok
500     @retval     != 0    error
501 */
502 
debug_sync_init(void)503 int debug_sync_init(void)
504 {
505   DBUG_ENTER("debug_sync_init");
506 
507 #ifdef HAVE_PSI_INTERFACE
508   init_debug_sync_psi_keys();
509 #endif
510 
511   if (opt_debug_sync_timeout)
512   {
513     int rc;
514 
515     /* Initialize the global variables. */
516     if ((rc= mysql_cond_init(key_debug_sync_globals_ds_cond,
517                              &debug_sync_global.ds_cond, NULL)) ||
518         (rc= mysql_mutex_init(key_debug_sync_globals_ds_mutex,
519                               &debug_sync_global.ds_mutex,
520                               MY_MUTEX_INIT_FAST)))
521       DBUG_RETURN(rc); /* purecov: inspected */
522 
523     /* Set the call back pointer in C files. */
524     debug_sync_C_callback_ptr= debug_sync_C_callback;
525   }
526 
527   DBUG_RETURN(0);
528 }
529 
530 
531 /**
532   End the debug sync facility.
533 
534   @description
535     This is called at server shutdown or after a thread initialization error.
536 */
537 
debug_sync_end(void)538 void debug_sync_end(void)
539 {
540   DBUG_ENTER("debug_sync_end");
541 
542   /* End the facility only if it had been initialized. */
543   if (debug_sync_C_callback_ptr)
544   {
545     /* Clear the call back pointer in C files. */
546     debug_sync_C_callback_ptr= NULL;
547 
548     /* Destroy the global variables. */
549     debug_sync_global.ds_signal_set.clear();
550     mysql_cond_destroy(&debug_sync_global.ds_cond);
551     mysql_mutex_destroy(&debug_sync_global.ds_mutex);
552 
553     /* Print statistics. */
554     {
555       char llbuff[22];
556       sql_print_information("Debug sync points hit:                   %22s",
557                             llstr(debug_sync_global.dsp_hits, llbuff));
558       sql_print_information("Debug sync points executed:              %22s",
559                             llstr(debug_sync_global.dsp_executed, llbuff));
560       sql_print_information("Debug sync points max active per thread: %22s",
561                             llstr(debug_sync_global.dsp_max_active, llbuff));
562     }
563   }
564 
565   DBUG_VOID_RETURN;
566 }
567 
568 
569 /* purecov: begin tested */
570 
571 /**
572   Disable the facility after lack of memory if no error can be returned.
573 
574   @note
575     Do not end the facility here because the global variables can
576     be in use by other threads.
577 */
578 
debug_sync_emergency_disable(void)579 static void debug_sync_emergency_disable(void)
580 {
581   DBUG_ENTER("debug_sync_emergency_disable");
582 
583   opt_debug_sync_timeout= 0;
584 
585   DBUG_PRINT("debug_sync",
586              ("Debug Sync Facility disabled due to lack of memory."));
587   sql_print_error("Debug Sync Facility disabled due to lack of memory.");
588 
589   DBUG_VOID_RETURN;
590 }
591 
592 /* purecov: end */
593 
594 
595 /**
596   Initialize the debug sync facility at thread start.
597 
598   @param[in]    thd             thread handle
599 */
600 
debug_sync_init_thread(THD * thd)601 void debug_sync_init_thread(THD *thd)
602 {
603   DBUG_ENTER("debug_sync_init_thread");
604   DBUG_ASSERT(thd);
605 
606   if (opt_debug_sync_timeout)
607   {
608     thd->debug_sync_control= (st_debug_sync_control*)
609       my_malloc(sizeof(st_debug_sync_control), MYF(MY_WME | MY_ZEROFILL));
610     if (!thd->debug_sync_control)
611     {
612       /*
613         Error is reported by my_malloc().
614         We must disable the facility. We have no way to return an error.
615       */
616       debug_sync_emergency_disable(); /* purecov: tested */
617     }
618   }
619 
620   DBUG_VOID_RETURN;
621 }
622 
623 
624 /**
625   End the debug sync facility at thread end.
626 
627   @param[in]    thd             thread handle
628 */
629 
debug_sync_end_thread(THD * thd)630 void debug_sync_end_thread(THD *thd)
631 {
632   DBUG_ENTER("debug_sync_end_thread");
633   DBUG_ASSERT(thd);
634 
635   if (thd->debug_sync_control)
636   {
637     st_debug_sync_control *ds_control= thd->debug_sync_control;
638 
639     /*
640       This synchronization point can be used to synchronize on thread end.
641       This is the latest point in a THD's life, where this can be done.
642     */
643     DEBUG_SYNC(thd, "thread_end");
644 
645     if (ds_control->ds_action)
646     {
647       st_debug_sync_action *action= ds_control->ds_action;
648       st_debug_sync_action *action_end= action + ds_control->ds_allocated;
649       for (; action < action_end; action++)
650       {
651         action->signal.free();
652         action->wait_for.free();
653         action->sync_point.free();
654       }
655       my_free(ds_control->ds_action);
656     }
657 
658     /* Statistics. */
659     mysql_mutex_lock(&debug_sync_global.ds_mutex);
660     debug_sync_global.dsp_hits+=           ds_control->dsp_hits;
661     debug_sync_global.dsp_executed+=       ds_control->dsp_executed;
662     if (debug_sync_global.dsp_max_active < ds_control->dsp_max_active)
663       debug_sync_global.dsp_max_active=    ds_control->dsp_max_active;
664     mysql_mutex_unlock(&debug_sync_global.ds_mutex);
665 
666     my_free(ds_control);
667     thd->debug_sync_control= NULL;
668   }
669 
670   DBUG_VOID_RETURN;
671 }
672 
673 
674 /**
675   Move a string by length.
676 
677   @param[out]   to              buffer for the resulting string
678   @param[in]    to_end          end of buffer
679   @param[in]    from            source string
680   @param[in]    length          number of bytes to copy
681 
682   @return       pointer to end of copied string
683 */
684 
debug_sync_bmove_len(char * to,char * to_end,const char * from,size_t length)685 static char *debug_sync_bmove_len(char *to, char *to_end,
686                                   const char *from, size_t length)
687 {
688   DBUG_ASSERT(to);
689   DBUG_ASSERT(to_end);
690   DBUG_ASSERT(!length || from);
691   set_if_smaller(length, (size_t) (to_end - to));
692   memcpy(to, from, length);
693   return (to + length);
694 }
695 
696 
697 #if !defined(DBUG_OFF)
698 
699 /**
700   Create a string that describes an action.
701 
702   @param[out]   result          buffer for the resulting string
703   @param[in]    size            size of result buffer
704   @param[in]    action          action to describe
705 */
706 
debug_sync_action_string(char * result,uint size,st_debug_sync_action * action)707 static void debug_sync_action_string(char *result, uint size,
708                                      st_debug_sync_action *action)
709 {
710   char  *wtxt= result;
711   char  *wend= wtxt + size - 1; /* Allow emergency '\0'. */
712   DBUG_ASSERT(result);
713   DBUG_ASSERT(action);
714 
715   /* If an execute count is present, signal or wait_for are needed too. */
716   DBUG_ASSERT(!action->execute ||
717               action->signal.length() || action->wait_for.length());
718 
719   if (action->execute)
720   {
721     if (action->signal.length())
722     {
723       wtxt= debug_sync_bmove_len(wtxt, wend, STRING_WITH_LEN("SIGNAL "));
724       wtxt= debug_sync_bmove_len(wtxt, wend, action->signal.ptr(),
725                                  action->signal.length());
726     }
727     if (action->wait_for.length())
728     {
729       if ((wtxt == result) && (wtxt < wend))
730         *(wtxt++)= ' ';
731       wtxt= debug_sync_bmove_len(wtxt, wend, STRING_WITH_LEN(" WAIT_FOR "));
732       wtxt= debug_sync_bmove_len(wtxt, wend, action->wait_for.ptr(),
733                                  action->wait_for.length());
734 
735       if (action->timeout != opt_debug_sync_timeout)
736       {
737         wtxt+= my_snprintf(wtxt, wend - wtxt, " TIMEOUT %lu", action->timeout);
738       }
739     }
740     if (action->execute != 1)
741     {
742       wtxt+= my_snprintf(wtxt, wend - wtxt, " EXECUTE %lu", action->execute);
743     }
744   }
745   if (action->hit_limit)
746   {
747     wtxt+= my_snprintf(wtxt, wend - wtxt, "%sHIT_LIMIT %lu",
748                        (wtxt == result) ? "" : " ", action->hit_limit);
749   }
750 
751   /*
752     If (wtxt == wend) string may not be terminated.
753     There is one byte left for an emergency termination.
754   */
755   *wtxt= '\0';
756 }
757 
758 
759 /**
760   Print actions.
761 
762   @param[in]    thd             thread handle
763 */
764 
debug_sync_print_actions(THD * thd)765 static void debug_sync_print_actions(THD *thd)
766 {
767   st_debug_sync_control *ds_control= thd->debug_sync_control;
768   uint                  idx;
769   DBUG_ENTER("debug_sync_print_actions");
770   DBUG_ASSERT(thd);
771 
772   if (!ds_control)
773     DBUG_VOID_RETURN;
774 
775   for (idx= 0; idx < ds_control->ds_active; idx++)
776   {
777     const char *dsp_name= ds_control->ds_action[idx].sync_point.c_ptr();
778     char action_string[256];
779 
780     debug_sync_action_string(action_string, sizeof(action_string),
781                              ds_control->ds_action + idx);
782     DBUG_PRINT("debug_sync_list", ("%s %s", dsp_name, action_string));
783   }
784 
785   DBUG_VOID_RETURN;
786 }
787 
788 #endif /* !defined(DBUG_OFF) */
789 
790 
791 /**
792   Compare two actions by sync point name length, string.
793 
794   @param[in]    arg1            reference to action1
795   @param[in]    arg2            reference to action2
796 
797   @return       difference
798     @retval     == 0            length1/string1 is same as length2/string2
799     @retval     < 0             length1/string1 is smaller
800     @retval     > 0             length1/string1 is bigger
801 */
802 
debug_sync_qsort_cmp(const void * arg1,const void * arg2)803 static int debug_sync_qsort_cmp(const void* arg1, const void* arg2)
804 {
805   st_debug_sync_action *action1= (st_debug_sync_action*) arg1;
806   st_debug_sync_action *action2= (st_debug_sync_action*) arg2;
807   int diff;
808   DBUG_ASSERT(action1);
809   DBUG_ASSERT(action2);
810 
811   if (!(diff= action1->sync_point.length() - action2->sync_point.length()))
812     diff= memcmp(action1->sync_point.ptr(), action2->sync_point.ptr(),
813                  action1->sync_point.length());
814 
815   return diff;
816 }
817 
818 
819 /**
820   Find a debug sync action.
821 
822   @param[in]    actionarr       array of debug sync actions
823   @param[in]    quantity        number of actions in array
824   @param[in]    dsp_name        name of debug sync point to find
825   @param[in]    name_len        length of name of debug sync point
826 
827   @return       action
828     @retval     != NULL         found sync point in array
829     @retval     NULL            not found
830 
831   @description
832     Binary search. Array needs to be sorted by length, sync point name.
833 */
834 
debug_sync_find(st_debug_sync_action * actionarr,int quantity,const char * dsp_name,uint name_len)835 static st_debug_sync_action *debug_sync_find(st_debug_sync_action *actionarr,
836                                              int quantity,
837                                              const char *dsp_name,
838                                              uint name_len)
839 {
840   st_debug_sync_action  *action;
841   int                   low ;
842   int                   high ;
843   int                   mid ;
844   int                   diff ;
845   DBUG_ASSERT(actionarr);
846   DBUG_ASSERT(dsp_name);
847   DBUG_ASSERT(name_len);
848 
849   low= 0;
850   high= quantity;
851 
852   while (low < high)
853   {
854     mid= (low + high) / 2;
855     action= actionarr + mid;
856     if (!(diff= name_len - action->sync_point.length()) &&
857         !(diff= memcmp(dsp_name, action->sync_point.ptr(), name_len)))
858       return action;
859     if (diff > 0)
860       low= mid + 1;
861     else
862       high= mid - 1;
863   }
864 
865   if (low < quantity)
866   {
867     action= actionarr + low;
868     if ((name_len == action->sync_point.length()) &&
869         !memcmp(dsp_name, action->sync_point.ptr(), name_len))
870       return action;
871   }
872 
873   return NULL;
874 }
875 
876 
877 /**
878   Reset the debug sync facility.
879 
880   @param[in]    thd             thread handle
881 
882   @description
883     Remove all actions of this thread.
884     Clear the global signal.
885 */
886 
debug_sync_reset(THD * thd)887 static void debug_sync_reset(THD *thd)
888 {
889   st_debug_sync_control *ds_control= thd->debug_sync_control;
890   DBUG_ENTER("debug_sync_reset");
891   DBUG_ASSERT(thd);
892   DBUG_ASSERT(ds_control);
893 
894   /* Remove all actions of this thread. */
895   ds_control->ds_active= 0;
896 
897   /* Clear the signals. */
898   mysql_mutex_lock(&debug_sync_global.ds_mutex);
899   debug_sync_global.ds_signal_set.clear();
900   mysql_mutex_unlock(&debug_sync_global.ds_mutex);
901 
902   DBUG_VOID_RETURN;
903 }
904 
905 
906 /**
907   Remove a debug sync action.
908 
909   @param[in]    ds_control      control object
910   @param[in]    action          action to be removed
911 
912   @description
913     Removing an action mainly means to decrement the ds_active counter.
914     But if the action is between other active action in the array, then
915     the array needs to be shrinked. The active actions above the one to
916     be removed have to be moved down by one slot.
917 */
918 
debug_sync_remove_action(st_debug_sync_control * ds_control,st_debug_sync_action * action)919 static void debug_sync_remove_action(st_debug_sync_control *ds_control,
920                                      st_debug_sync_action *action)
921 {
922   uint dsp_idx= action - ds_control->ds_action;
923   DBUG_ENTER("debug_sync_remove_action");
924   DBUG_ASSERT(ds_control);
925   DBUG_ASSERT(ds_control == current_thd->debug_sync_control);
926   DBUG_ASSERT(action);
927   DBUG_ASSERT(dsp_idx < ds_control->ds_active);
928 
929   /* Decrement the number of currently active actions. */
930   ds_control->ds_active--;
931 
932   /*
933     If this was not the last active action in the array, we need to
934     shift remaining active actions down to keep the array gap-free.
935     Otherwise binary search might fail or take longer than necessary at
936     least. Also new actions are always put to the end of the array.
937   */
938   if (ds_control->ds_active > dsp_idx)
939   {
940     /*
941       Do not make save_action an object of class st_debug_sync_action.
942       Its destructor would tamper with the String pointers.
943     */
944     uchar save_action[sizeof(st_debug_sync_action)];
945 
946     /*
947       Copy the to-be-removed action object to temporary storage before
948       the shift copies the string pointers over. Do not use assignment
949       because it would use assignment operator methods for the Strings.
950       This would copy the strings. The shift below overwrite the string
951       pointers without freeing them first. By using memmove() we save
952       the pointers, which are overwritten by the shift.
953     */
954     memmove(save_action, action, sizeof(st_debug_sync_action));
955 
956     /* Move actions down. */
957     memmove(static_cast<void*>(ds_control->ds_action + dsp_idx),
958             ds_control->ds_action + dsp_idx + 1,
959             (ds_control->ds_active - dsp_idx) *
960             sizeof(st_debug_sync_action));
961 
962     /*
963       Copy back the saved action object to the now free array slot. This
964       replaces the double references of String pointers that have been
965       produced by the shift. Again do not use an assignment operator to
966       avoid string allocation/copy.
967     */
968     memmove(static_cast<void*>(ds_control->ds_action + ds_control->ds_active),
969             save_action, sizeof(st_debug_sync_action));
970   }
971 
972   DBUG_VOID_RETURN;
973 }
974 
975 
976 /**
977   Get a debug sync action.
978 
979   @param[in]    thd             thread handle
980   @param[in]    dsp_name        debug sync point name
981   @param[in]    name_len        length of sync point name
982 
983   @return       action
984     @retval     != NULL         ok
985     @retval     NULL            error
986 
987   @description
988     Find the debug sync action for a debug sync point or make a new one.
989 */
990 
debug_sync_get_action(THD * thd,const char * dsp_name,uint name_len)991 static st_debug_sync_action *debug_sync_get_action(THD *thd,
992                                                    const char *dsp_name,
993                                                    uint name_len)
994 {
995   st_debug_sync_control *ds_control= thd->debug_sync_control;
996   st_debug_sync_action  *action;
997   DBUG_ENTER("debug_sync_get_action");
998   DBUG_ASSERT(thd);
999   DBUG_ASSERT(dsp_name);
1000   DBUG_ASSERT(name_len);
1001   DBUG_ASSERT(ds_control);
1002   DBUG_PRINT("debug_sync", ("sync_point: '%.*s'", (int) name_len, dsp_name));
1003   DBUG_PRINT("debug_sync", ("active: %u  allocated: %u",
1004                             ds_control->ds_active, ds_control->ds_allocated));
1005 
1006   /* There cannot be more active actions than allocated. */
1007   DBUG_ASSERT(ds_control->ds_active <= ds_control->ds_allocated);
1008   /* If there are active actions, the action array must be present. */
1009   DBUG_ASSERT(!ds_control->ds_active || ds_control->ds_action);
1010 
1011   /* Try to reuse existing action if there is one for this sync point. */
1012   if (ds_control->ds_active &&
1013       (action= debug_sync_find(ds_control->ds_action, ds_control->ds_active,
1014                                dsp_name, name_len)))
1015   {
1016     /* Reuse an already active sync point action. */
1017     DBUG_ASSERT((uint)(action - ds_control->ds_action) < ds_control->ds_active);
1018     DBUG_PRINT("debug_sync", ("reuse action idx: %ld",
1019                               (long) (action - ds_control->ds_action)));
1020   }
1021   else
1022   {
1023     /* Create a new action. */
1024     int dsp_idx= ds_control->ds_active++;
1025     set_if_bigger(ds_control->dsp_max_active, ds_control->ds_active);
1026     if (ds_control->ds_active > ds_control->ds_allocated)
1027     {
1028       uint new_alloc= ds_control->ds_active + 3;
1029       void *new_action= my_realloc(ds_control->ds_action,
1030                                    new_alloc * sizeof(st_debug_sync_action),
1031                                    MYF(MY_WME | MY_ALLOW_ZERO_PTR));
1032       if (!new_action)
1033       {
1034         /* Error is reported by my_malloc(). */
1035         goto err; /* purecov: tested */
1036       }
1037       ds_control->ds_action= (st_debug_sync_action*) new_action;
1038       ds_control->ds_allocated= new_alloc;
1039       /* Clear memory as we do not run string constructors here. */
1040       memset(static_cast<void*>(ds_control->ds_action + dsp_idx), 0,
1041             (new_alloc - dsp_idx) * sizeof(st_debug_sync_action));
1042     }
1043     DBUG_PRINT("debug_sync", ("added action idx: %u", dsp_idx));
1044     action= ds_control->ds_action + dsp_idx;
1045     if (action->sync_point.copy(dsp_name, name_len, system_charset_info))
1046     {
1047       /* Error is reported by my_malloc(). */
1048       goto err; /* purecov: tested */
1049     }
1050     action->need_sort= TRUE;
1051   }
1052   DBUG_ASSERT(action >= ds_control->ds_action);
1053   DBUG_ASSERT(action < ds_control->ds_action + ds_control->ds_active);
1054   DBUG_PRINT("debug_sync", ("action: 0x%lx  array: 0x%lx  count: %u",
1055                             (long) action, (long) ds_control->ds_action,
1056                             ds_control->ds_active));
1057 
1058   DBUG_RETURN(action);
1059 
1060   /* purecov: begin tested */
1061  err:
1062   DBUG_RETURN(NULL);
1063   /* purecov: end */
1064 }
1065 
1066 
1067 /**
1068   Set a debug sync action.
1069 
1070   @param[in]    thd             thread handle
1071   @param[in]    action          synchronization action
1072 
1073   @return       status
1074     @retval     FALSE           ok
1075     @retval     TRUE            error
1076 
1077   @description
1078     This is called from the debug sync parser. It arms the action for
1079     the requested sync point. If the action parsed into an empty action,
1080     it is removed instead.
1081 
1082     Setting an action for a sync point means to make the sync point
1083     active. When it is hit it will execute this action.
1084 
1085     Before parsing, we "get" an action object. This is placed at the
1086     end of the thread's action array unless the requested sync point
1087     has an action already.
1088 
1089     Then the parser fills the action object from the request string.
1090 
1091     Finally the action is "set" for the sync point. If it was parsed
1092     to be empty, it is removed from the array. If it did belong to a
1093     sync point before, the sync point becomes inactive. If the action
1094     became non-empty and it did not belong to a sync point before (it
1095     was added at the end of the action array), the action array needs
1096     to be sorted by sync point.
1097 
1098     If the sync point name is "now", it is executed immediately.
1099 */
1100 
debug_sync_set_action(THD * thd,st_debug_sync_action * action)1101 static bool debug_sync_set_action(THD *thd, st_debug_sync_action *action)
1102 {
1103   st_debug_sync_control *ds_control= thd->debug_sync_control;
1104   bool is_dsp_now= FALSE;
1105   DBUG_ENTER("debug_sync_set_action");
1106   DBUG_ASSERT(thd);
1107   DBUG_ASSERT(action);
1108   DBUG_ASSERT(ds_control);
1109 
1110   action->activation_count= max(action->hit_limit, action->execute);
1111   if (!action->activation_count)
1112   {
1113     debug_sync_remove_action(ds_control, action);
1114     DBUG_PRINT("debug_sync", ("action cleared"));
1115   }
1116   else
1117   {
1118     const char *dsp_name= action->sync_point.c_ptr();
1119     DBUG_EXECUTE("debug_sync", {
1120         /* Functions as DBUG_PRINT args can change keyword and line nr. */
1121         const char *sig_emit= action->signal.c_ptr();
1122         const char *sig_wait= action->wait_for.c_ptr();
1123         DBUG_PRINT("debug_sync",
1124                    ("sync_point: '%s'  activation_count: %lu  hit_limit: %lu  "
1125                     "execute: %lu  timeout: %lu  signal: '%s'  wait_for: '%s'",
1126                     dsp_name, action->activation_count,
1127                     action->hit_limit, action->execute, action->timeout,
1128                     sig_emit, sig_wait));});
1129 
1130     /* Check this before sorting the array. action may move. */
1131     is_dsp_now= !my_strcasecmp(system_charset_info, dsp_name, "now");
1132 
1133     if (action->need_sort)
1134     {
1135       action->need_sort= FALSE;
1136       /* Sort actions by (name_len, name). */
1137       my_qsort(ds_control->ds_action, ds_control->ds_active,
1138                sizeof(st_debug_sync_action), debug_sync_qsort_cmp);
1139     }
1140   }
1141   DBUG_EXECUTE("debug_sync_list", debug_sync_print_actions(thd););
1142 
1143   /* Execute the special sync point 'now' if activated above. */
1144   if (is_dsp_now)
1145   {
1146     DEBUG_SYNC(thd, "now");
1147     /*
1148       If HIT_LIMIT for sync point "now" was 1, the execution of the sync
1149       point decremented it to 0. In this case the following happened:
1150 
1151       - an error message was reported with my_error() and
1152       - the statement was killed with thd->killed= THD::KILL_QUERY.
1153 
1154       If a statement reports an error, it must not call send_ok().
1155       The calling functions will not call send_ok(), if we return TRUE
1156       from this function.
1157 
1158       thd->killed is also set if the wait is interrupted from a
1159       KILL or KILL QUERY statement. In this case, no error is reported
1160       and shall not be reported as a result of SET DEBUG_SYNC.
1161       Hence, we check for the first condition above.
1162     */
1163     if (thd->is_error())
1164       DBUG_RETURN(TRUE);
1165   }
1166 
1167   DBUG_RETURN(FALSE);
1168 }
1169 
1170 
1171 /*
1172   Advance the pointer by length of multi-byte character.
1173 
1174     @param    ptr   pointer to multibyte character.
1175 
1176     @return   NULL or pointer after advancing pointer by the
1177               length of multi-byte character pointed to.
1178 */
1179 
advance_mbchar_ptr(const char * ptr)1180 static inline const char *advance_mbchar_ptr(const char *ptr)
1181 {
1182   uint clen= my_mbcharlen(system_charset_info, (uchar) *ptr);
1183 
1184   return (clen != 0) ? ptr + clen : NULL;
1185 }
1186 
1187 
1188 /*
1189   Skip whitespace characters from the beginning of the multi-byte string.
1190 
1191   @param    ptr     pointer to the multi-byte string.
1192 
1193   @return   a pointer to the first non-whitespace character or NULL if the
1194             string consists from whitespace characters only.
1195 */
1196 
skip_whitespace(const char * ptr)1197 static inline const char *skip_whitespace(const char *ptr)
1198 {
1199   while (ptr != NULL && *ptr && my_isspace(system_charset_info, *ptr))
1200     ptr= advance_mbchar_ptr(ptr);
1201 
1202   return ptr;
1203 }
1204 
1205 
1206 /*
1207   Get pointer to end of token.
1208 
1209   @param    ptr  pointer to start of token
1210 
1211   @return   NULL or pointer to end of token.
1212 */
1213 
get_token_end_ptr(const char * ptr)1214 static inline const char *get_token_end_ptr(const char *ptr)
1215 {
1216   while (ptr != NULL && *ptr && !my_isspace(system_charset_info, *ptr))
1217     ptr= advance_mbchar_ptr(ptr);
1218 
1219   return ptr;
1220 }
1221 
1222 
1223 /**
1224   Extract a token from a string.
1225 
1226   @param[out]     token_p         returns start of token
1227   @param[out]     token_length_p  returns length of token
1228   @param[in,out]  ptr             current string pointer, adds '\0' terminators
1229 
1230   @return       string pointer or NULL
1231     @retval     != NULL         ptr behind token terminator or at string end
1232     @retval     NULL            no token found in remainder of string
1233 
1234   @note
1235     This function assumes that the string is in system_charset_info,
1236     that this charset is single byte for ASCII NUL ('\0'), that no
1237     character except of ASCII NUL ('\0') contains a byte with value 0,
1238     and that ASCII NUL ('\0') is used as the string terminator.
1239 
1240     This function needs to return tokens that are terminated with ASCII
1241     NUL ('\0'). The tokens are used in my_strcasecmp(). Unfortunately
1242     there is no my_strncasecmp().
1243 
1244     To return the last token without copying it, we require the input
1245     string to be nul terminated.
1246 
1247   @description
1248     This function skips space characters at string begin.
1249 
1250     It returns a pointer to the first non-space character in *token_p.
1251 
1252     If no non-space character is found before the string terminator
1253     ASCII NUL ('\0'), the function returns NULL. *token_p and
1254     *token_length_p remain unchanged in this case (they are not set).
1255 
1256     The function takes a space character or an ASCII NUL ('\0') as a
1257     terminator of the token. The space character could be multi-byte.
1258 
1259     It returns the length of the token in bytes, excluding the
1260     terminator, in *token_length_p.
1261 
1262     If the terminator of the token is ASCII NUL ('\0'), it returns a
1263     pointer to the terminator (string end).
1264 
1265     If the terminator is a space character, it replaces the the first
1266     byte of the terminator character by ASCII NUL ('\0'), skips the (now
1267     corrupted) terminator character, and skips all following space
1268     characters. It returns a pointer to the next non-space character or
1269     to the string terminator ASCII NUL ('\0').
1270 */
1271 
debug_sync_token(char ** token_p,uint * token_length_p,char * ptr)1272 static char *debug_sync_token(char **token_p, uint *token_length_p, char *ptr)
1273 {
1274   DBUG_ASSERT(token_p);
1275   DBUG_ASSERT(token_length_p);
1276   DBUG_ASSERT(ptr);
1277 
1278 
1279   /* Skip leading space */
1280   ptr= const_cast<char*>(skip_whitespace(ptr));
1281 
1282   if (ptr == NULL || !*ptr)
1283     return NULL;
1284 
1285   /* Get token start. */
1286   *token_p= ptr;
1287 
1288   /* Find token end. */
1289   ptr= const_cast<char*>(get_token_end_ptr(ptr));
1290 
1291   if (ptr == NULL)
1292     return NULL;
1293 
1294   /* Get token length. */
1295   *token_length_p= ptr - *token_p;
1296 
1297   /* If necessary, terminate token. */
1298   if (*ptr)
1299   {
1300      char* tmp= ptr;
1301 
1302     /* Advance by terminator character length. */
1303     ptr= const_cast<char*>(advance_mbchar_ptr(ptr));
1304     if (ptr != NULL)
1305     {
1306       /* Terminate token. */
1307       *tmp= '\0';
1308 
1309       /* Skip trailing space */
1310       ptr= const_cast<char*>(skip_whitespace(ptr));
1311     }
1312   }
1313   return ptr;
1314 }
1315 
1316 
1317 /**
1318   Extract a number from a string.
1319 
1320   @param[out]   number_p        returns number
1321   @param[in]    actstrptr       current pointer in action string
1322 
1323   @return       string pointer or NULL
1324     @retval     != NULL         ptr behind token terminator or at string end
1325     @retval     NULL            no token found or token is not valid number
1326 
1327   @note
1328     The same assumptions about charset apply as for debug_sync_token().
1329 
1330   @description
1331     This function fetches a token from the string and converts it
1332     into a number.
1333 
1334     If there is no token left in the string, or the token is not a valid
1335     decimal number, NULL is returned. The result in *number_p is
1336     undefined in this case.
1337 */
1338 
debug_sync_number(ulong * number_p,char * actstrptr)1339 static char *debug_sync_number(ulong *number_p, char *actstrptr)
1340 {
1341   char                  *ptr;
1342   char                  *ept;
1343   char                  *token;
1344   uint                  token_length;
1345   DBUG_ASSERT(number_p);
1346   DBUG_ASSERT(actstrptr);
1347 
1348   /* Get token from string. */
1349   if (!(ptr= debug_sync_token(&token, &token_length, actstrptr)))
1350     goto end;
1351 
1352   *number_p= strtoul(token, &ept, 10);
1353   if (*ept)
1354     ptr= NULL;
1355 
1356  end:
1357   return ptr;
1358 }
1359 
1360 
1361 /**
1362   Evaluate a debug sync action string.
1363 
1364   @param[in]        thd             thread handle
1365   @param[in,out]    action_str      action string to receive '\0' terminators
1366 
1367   @return           status
1368     @retval         FALSE           ok
1369     @retval         TRUE            error
1370 
1371   @description
1372     This is called when the DEBUG_SYNC system variable is set.
1373     Parse action string, build a debug sync action, activate it.
1374 
1375     Before parsing, we "get" an action object. This is placed at the
1376     end of the thread's action array unless the requested sync point
1377     has an action already.
1378 
1379     Then the parser fills the action object from the request string.
1380 
1381     Finally the action is "set" for the sync point. This means that the
1382     sync point becomes active or inactive, depending on the action
1383     values.
1384 
1385   @note
1386     The input string needs to be ASCII NUL ('\0') terminated. We split
1387     nul-terminated tokens in it without copy.
1388 
1389   @see the function comment of debug_sync_token() for more constraints
1390     for the string.
1391 */
1392 
debug_sync_eval_action(THD * thd,char * action_str)1393 static bool debug_sync_eval_action(THD *thd, char *action_str)
1394 {
1395   st_debug_sync_action  *action= NULL;
1396   const char            *errmsg;
1397   char                  *ptr;
1398   char                  *token;
1399   uint                  token_length= 0;
1400   DBUG_ENTER("debug_sync_eval_action");
1401   DBUG_ASSERT(thd);
1402   DBUG_ASSERT(action_str);
1403 
1404   /*
1405     Get debug sync point name. Or a special command.
1406   */
1407   if (!(ptr= debug_sync_token(&token, &token_length, action_str)))
1408   {
1409     errmsg= "Missing synchronization point name";
1410     goto err;
1411   }
1412 
1413   /*
1414     If there is a second token, the first one is the sync point name.
1415   */
1416   if (*ptr)
1417   {
1418     /* Get an action object to collect the requested action parameters. */
1419     action= debug_sync_get_action(thd, token, token_length);
1420     if (!action)
1421     {
1422       /* Error message is sent. */
1423       DBUG_RETURN(TRUE); /* purecov: tested */
1424     }
1425   }
1426 
1427   /*
1428     Get kind of action to be taken at sync point.
1429   */
1430   if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1431   {
1432     /* No action present. Try special commands. Token unchanged. */
1433 
1434     /*
1435       Try RESET.
1436     */
1437     if (!my_strcasecmp(system_charset_info, token, "RESET"))
1438     {
1439       /* It is RESET. Reset all actions and global signal. */
1440       debug_sync_reset(thd);
1441       goto end;
1442     }
1443 
1444     /* Token unchanged. It still contains sync point name. */
1445     errmsg= "Missing action after synchronization point name '%.*s'";
1446     goto err;
1447   }
1448 
1449   /*
1450     Check for pseudo actions first. Start with actions that work on
1451     an existing action.
1452   */
1453   DBUG_ASSERT(action);
1454 
1455   /*
1456     Try TEST.
1457   */
1458   if (!my_strcasecmp(system_charset_info, token, "TEST"))
1459   {
1460     /* It is TEST. Nothing must follow it. */
1461     if (*ptr)
1462     {
1463       errmsg= "Nothing must follow action TEST";
1464       goto err;
1465     }
1466 
1467     /* Execute sync point. */
1468     debug_sync(thd, action->sync_point.ptr(), action->sync_point.length());
1469     /* Fix statistics. This was not a real hit of the sync point. */
1470     thd->debug_sync_control->dsp_hits--;
1471     goto end;
1472   }
1473 
1474   /*
1475     Now check for actions that define a new action.
1476     Initialize action. Do not use memset(). Strings may have malloced.
1477   */
1478   action->activation_count= 0;
1479   action->hit_limit= 0;
1480   action->execute= 0;
1481   action->timeout= 0;
1482   action->signal.length(0);
1483   action->wait_for.length(0);
1484 
1485   /*
1486     Try CLEAR.
1487   */
1488   if (!my_strcasecmp(system_charset_info, token, "CLEAR"))
1489   {
1490     /* It is CLEAR. Nothing must follow it. */
1491     if (*ptr)
1492     {
1493       errmsg= "Nothing must follow action CLEAR";
1494       goto err;
1495     }
1496 
1497     /* Set (clear/remove) action. */
1498     goto set_action;
1499   }
1500 
1501   /*
1502     Now check for real sync point actions.
1503   */
1504 
1505   /*
1506     Try SIGNAL.
1507   */
1508   if (!my_strcasecmp(system_charset_info, token, "SIGNAL"))
1509   {
1510     /* It is SIGNAL. Signal name must follow. */
1511     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1512     {
1513       errmsg= "Missing signal name after action SIGNAL";
1514       goto err;
1515     }
1516     if (action->signal.copy(token, token_length, system_charset_info))
1517     {
1518       /* Error is reported by my_malloc(). */
1519       /* purecov: begin tested */
1520       errmsg= NULL;
1521       goto err;
1522       /* purecov: end */
1523     }
1524 
1525     /* Set default for EXECUTE option. */
1526     action->execute= 1;
1527 
1528     /* Get next token. If none follows, set action. */
1529     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1530       goto set_action;
1531   }
1532 
1533   /*
1534     Try WAIT_FOR.
1535   */
1536   if (!my_strcasecmp(system_charset_info, token, "WAIT_FOR"))
1537   {
1538     /* It is WAIT_FOR. Wait_for signal name must follow. */
1539     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1540     {
1541       errmsg= "Missing signal name after action WAIT_FOR";
1542       goto err;
1543     }
1544     if (action->wait_for.copy(token, token_length, system_charset_info))
1545     {
1546       /* Error is reported by my_malloc(). */
1547       /* purecov: begin tested */
1548       errmsg= NULL;
1549       goto err;
1550       /* purecov: end */
1551     }
1552 
1553     /* Set default for EXECUTE and TIMEOUT options. */
1554     action->execute= 1;
1555     action->timeout= opt_debug_sync_timeout;
1556     action->clear_event= true;
1557 
1558     /* Get next token. If none follows, set action. */
1559     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1560       goto set_action;
1561 
1562     /*
1563       Try TIMEOUT.
1564     */
1565     if (!my_strcasecmp(system_charset_info, token, "TIMEOUT"))
1566     {
1567       /* It is TIMEOUT. Number must follow. */
1568       if (!(ptr= debug_sync_number(&action->timeout, ptr)))
1569       {
1570         errmsg= "Missing valid number after TIMEOUT";
1571         goto err;
1572       }
1573 
1574       /* Get next token. If none follows, set action. */
1575       if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1576         goto set_action;
1577     }
1578   }
1579 
1580   /*
1581     Try EXECUTE.
1582   */
1583   if (!my_strcasecmp(system_charset_info, token, "EXECUTE"))
1584   {
1585     /*
1586       EXECUTE requires either SIGNAL and/or WAIT_FOR to be present.
1587       In this case action->execute has been preset to 1.
1588     */
1589     if (!action->execute)
1590     {
1591       errmsg= "Missing action before EXECUTE";
1592       goto err;
1593     }
1594 
1595     /* Number must follow. */
1596     if (!(ptr= debug_sync_number(&action->execute, ptr)))
1597     {
1598       errmsg= "Missing valid number after EXECUTE";
1599       goto err;
1600     }
1601 
1602     /* Get next token. If none follows, set action. */
1603     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1604       goto set_action;
1605   }
1606 
1607   /*
1608     Try NO_CLEAR_EVENT.
1609   */
1610   if (!my_strcasecmp(system_charset_info, token, "NO_CLEAR_EVENT"))
1611   {
1612     action->clear_event= false;
1613     /* Get next token. If none follows, set action. */
1614     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1615       goto set_action;
1616   }
1617 
1618   /*
1619     Try HIT_LIMIT.
1620   */
1621   if (!my_strcasecmp(system_charset_info, token, "HIT_LIMIT"))
1622   {
1623     /* Number must follow. */
1624     if (!(ptr= debug_sync_number(&action->hit_limit, ptr)))
1625     {
1626       errmsg= "Missing valid number after HIT_LIMIT";
1627       goto err;
1628     }
1629 
1630     /* Get next token. If none follows, set action. */
1631     if (!(ptr= debug_sync_token(&token, &token_length, ptr)))
1632       goto set_action;
1633   }
1634 
1635   errmsg= "Illegal or out of order stuff: '%.*s'";
1636 
1637  err:
1638   if (errmsg)
1639   {
1640     /*
1641       NOTE: errmsg must either have %.*s or none % at all.
1642       It can be NULL if an error message is already reported
1643       (e.g. by my_malloc()).
1644     */
1645     set_if_smaller(token_length, 64); /* Limit error message length. */
1646     my_printf_error(ER_PARSE_ERROR, errmsg, MYF(0), token_length, token);
1647   }
1648   if (action)
1649     debug_sync_remove_action(thd->debug_sync_control, action);
1650   DBUG_RETURN(TRUE);
1651 
1652  set_action:
1653   DBUG_RETURN(debug_sync_set_action(thd, action));
1654 
1655  end:
1656   DBUG_RETURN(FALSE);
1657 }
1658 
1659 /**
1660   Set the system variable 'debug_sync'.
1661 
1662   @param[in]    thd             thread handle
1663   @param[in]    var             set variable request
1664 
1665   @return       status
1666     @retval     FALSE           ok, variable is set
1667     @retval     TRUE            error, variable could not be set
1668 
1669   @note
1670     "Setting" of the system variable 'debug_sync' does not mean to
1671     assign a value to it as usual. Instead a debug sync action is parsed
1672     from the input string and stored apart from the variable value.
1673 
1674   @note
1675     For efficiency reasons, the action string parser places '\0'
1676     terminators in the string. So we need to take a copy here.
1677 */
1678 
debug_sync_update(THD * thd,char * val_str)1679 bool debug_sync_update(THD *thd, char *val_str)
1680 {
1681   DBUG_ENTER("debug_sync_update");
1682   DBUG_PRINT("debug_sync", ("set action: '%s'", val_str));
1683 
1684   /*
1685     debug_sync_eval_action() places '\0' in the string, which itself
1686     must be '\0' terminated.
1687   */
1688   DBUG_RETURN(opt_debug_sync_timeout ?
1689               debug_sync_eval_action(thd, val_str) :
1690               FALSE);
1691 }
1692 
1693 
1694 /**
1695   Retrieve the value of the system variable 'debug_sync'.
1696 
1697   @param[in]    thd             thread handle
1698 
1699   @return       string
1700     @retval     != NULL         ok, string pointer
1701     @retval     NULL            memory allocation error
1702 
1703   @note
1704     The value of the system variable 'debug_sync' reflects if
1705     the facility is enabled ("ON") or disabled (default, "OFF").
1706 
1707     When "ON", the list of signals signalled are added separated by comma.
1708 */
1709 
debug_sync_value_ptr(THD * thd)1710 uchar *debug_sync_value_ptr(THD *thd)
1711 {
1712   char *value;
1713   DBUG_ENTER("debug_sync_value_ptr");
1714 
1715   if (opt_debug_sync_timeout)
1716   {
1717     std::string signals_on("ON - signals: '");
1718     static char sep[]= ",";
1719 
1720     // Ensure exclusive access to debug_sync_global.ds_signal_set
1721     mysql_mutex_lock(&debug_sync_global.ds_mutex);
1722 
1723     signal_event_set::const_iterator iter;
1724     for (iter= debug_sync_global.ds_signal_set.begin();
1725          iter != debug_sync_global.ds_signal_set.end(); )
1726     {
1727       signals_on.append(*iter);
1728       if ((++iter) != debug_sync_global.ds_signal_set.end())
1729         signals_on.append(sep);
1730     }
1731     signals_on.append("'");
1732 
1733     const char *c_str= signals_on.c_str();
1734     const size_t lgt= strlen(c_str) + 1;
1735 
1736     if ((value= (char*) alloc_root(thd->mem_root, lgt)))
1737       memcpy(value, c_str, lgt);
1738 
1739     mysql_mutex_unlock(&debug_sync_global.ds_mutex);
1740   }
1741   else
1742   {
1743     /* purecov: begin tested */
1744     value= const_cast<char*>("OFF");
1745     /* purecov: end */
1746   }
1747 
1748   DBUG_RETURN((uchar*) value);
1749 }
1750 
1751 
1752 /**
1753   Return true if the signal is found in global signal list.
1754 
1755   @param signal Signal name identifying the signal.
1756 
1757   @note
1758     If signal is found in the global signal set, it means that the
1759     signal thread has signalled to the waiting thread. This method
1760     must be called with the debug_sync_global.ds_mutex held.
1761 
1762   @eretval true  if signal is found in the global signal list.
1763   @retval false otherwise.
1764 */
1765 
is_signalled(const std::string * signal_name)1766 static inline bool is_signalled(const std::string *signal_name)
1767 {
1768   return (debug_sync_global.ds_signal_set.find(*signal_name) !=
1769           debug_sync_global.ds_signal_set.end());
1770 }
1771 
1772 
1773 /**
1774   Return false if signal has been added to global signal list.
1775 
1776   @param signal signal name that is to be added to the global signal
1777          list.
1778 
1779   @note
1780     This method add signal name to the global signal list and signals
1781     the waiting thread that this signal has been emitted. This method
1782     must be called with the debug_sync_global.ds_mutex held.
1783 */
1784 
add_signal_event(const std::string * signal_name)1785 static inline void add_signal_event(const std::string *signal_name)
1786 {
1787   debug_sync_global.ds_signal_set.insert(*signal_name);
1788 }
1789 
1790 
1791 /**
1792   Remove the signal from the global signal list.
1793 
1794   @param signal signal name to be removed from the global signal list.
1795 
1796   @note
1797     This method erases the signal from the signal list.  This happens
1798     when the wait thread has processed the signal event from the
1799     signalling thread. This method should be called with the
1800     debug_sync_global.ds_mutex held.
1801 */
clear_signal_event(const std::string * signal_name)1802 static inline void clear_signal_event(const std::string *signal_name)
1803 {
1804   debug_sync_global.ds_signal_set.erase(*signal_name);
1805 }
1806 
1807 
1808 /**
1809   Execute requested action at a synchronization point.
1810 
1811   @param[in]    thd                 thread handle
1812   @param[in]    action              action to be executed
1813 
1814   @note
1815     This is to be called only if activation count > 0.
1816 */
1817 
debug_sync_execute(THD * thd,st_debug_sync_action * action)1818 static void debug_sync_execute(THD *thd, st_debug_sync_action *action)
1819 {
1820 #ifndef DBUG_OFF
1821   const char *dsp_name= action->sync_point.c_ptr();
1822   const char *sig_emit= action->signal.c_ptr();
1823   const char *sig_wait= action->wait_for.c_ptr();
1824 #endif
1825   DBUG_ENTER("debug_sync_execute");
1826   DBUG_ASSERT(thd);
1827   DBUG_ASSERT(action);
1828   DBUG_PRINT("debug_sync",
1829              ("sync_point: '%s'  activation_count: %lu  hit_limit: %lu  "
1830               "execute: %lu  timeout: %lu  signal: '%s'  wait_for: '%s'",
1831               dsp_name, action->activation_count, action->hit_limit,
1832               action->execute, action->timeout, sig_emit, sig_wait));
1833 
1834   DBUG_ASSERT(action->activation_count);
1835   action->activation_count--;
1836 
1837   if (action->execute)
1838   {
1839     const char *UNINIT_VAR(old_proc_info);
1840 
1841     action->execute--;
1842 
1843     /*
1844       If we will be going to wait, set proc_info for the PROCESSLIST table.
1845       Do this before emitting the signal, so other threads can see it
1846       if they awake before we enter_cond() below.
1847     */
1848     if (action->wait_for.length())
1849     {
1850       st_debug_sync_control *ds_control= thd->debug_sync_control;
1851       strxnmov(ds_control->ds_proc_info, sizeof(ds_control->ds_proc_info)-1,
1852                "debug sync point: ", action->sync_point.c_ptr(), NullS);
1853       old_proc_info= thd->proc_info;
1854       thd_proc_info(thd, ds_control->ds_proc_info);
1855     }
1856 
1857     /*
1858       Take mutex to ensure that only one thread access
1859       debug_sync_global.ds_signal_set at a time.  Need to take mutex for
1860       read access too, to create a memory barrier in order to avoid that
1861       threads just reads an old cached version of the signal.
1862     */
1863     mysql_mutex_lock(&debug_sync_global.ds_mutex);
1864 
1865     if (action->signal.length())
1866     {
1867       std::string signal= action->signal.ptr();
1868       /* Copy the signal to the global set. */
1869       add_signal_event(&signal);
1870       /* Wake threads waiting in a sync point. */
1871       mysql_cond_broadcast(&debug_sync_global.ds_cond);
1872       DBUG_PRINT("debug_sync_exec", ("signal '%s'  at: '%s'",
1873                                      sig_emit, dsp_name));
1874     } /* end if (action->signal.length()) */
1875 
1876     if (action->wait_for.length())
1877     {
1878       mysql_mutex_t *old_mutex;
1879       mysql_cond_t  *old_cond= 0;
1880       int             error= 0;
1881       struct timespec abstime;
1882       std::string wait_for= action->wait_for.ptr();
1883 
1884       /*
1885         We don't use enter_cond()/exit_cond(). They do not save old
1886         mutex and cond. This would prohibit the use of DEBUG_SYNC
1887         between other places of enter_cond() and exit_cond().
1888 
1889         We need to check for existence of thd->mysys_var to also make
1890         it possible to use DEBUG_SYNC framework in scheduler when this
1891         variable has been set to NULL.
1892       */
1893       if (thd->mysys_var)
1894       {
1895         old_mutex= thd->mysys_var->current_mutex;
1896         old_cond= thd->mysys_var->current_cond;
1897         thd->mysys_var->current_mutex= &debug_sync_global.ds_mutex;
1898         thd->mysys_var->current_cond= &debug_sync_global.ds_cond;
1899       }
1900       else
1901         old_mutex= NULL;
1902 
1903       set_timespec(abstime, action->timeout);
1904       DBUG_EXECUTE("debug_sync_exec", {
1905           DBUG_PRINT("debug_sync_exec",
1906                      ("wait for '%s'  at: '%s'",
1907                       sig_wait, dsp_name));});
1908       /*
1909         Wait until global signal string matches the wait_for string.
1910         Interrupt when thread or query is killed or facility disabled.
1911         The facility can become disabled when some thread cannot get
1912         the required dynamic memory allocated.
1913       */
1914       while (!is_signalled(&wait_for) &&
1915              !thd->killed && opt_debug_sync_timeout)
1916       {
1917         error= mysql_cond_timedwait(&debug_sync_global.ds_cond,
1918                                     &debug_sync_global.ds_mutex,
1919                                     &abstime);
1920 
1921         DBUG_EXECUTE("debug_sync", {
1922             /* Functions as DBUG_PRINT args can change keyword and line nr. */
1923             DBUG_PRINT("debug_sync",
1924                        ("awoke from %s error: %d", sig_wait, error)); });
1925 
1926         if (error == ETIMEDOUT || error == ETIME)
1927         {
1928           // We should not make the statement fail, even if in strict mode.
1929           const bool save_abort_on_warning= thd->abort_on_warning;
1930           thd->abort_on_warning= false;
1931           push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
1932                        ER_DEBUG_SYNC_TIMEOUT, ER(ER_DEBUG_SYNC_TIMEOUT));
1933           thd->abort_on_warning= save_abort_on_warning;
1934           DBUG_EXECUTE_IF("debug_sync_abort_on_timeout", DBUG_ABORT(););
1935           break;
1936         }
1937         error= 0;
1938       }
1939       if (action->clear_event)
1940         clear_signal_event(&wait_for);
1941 
1942       DBUG_EXECUTE("debug_sync_exec",
1943                    if (thd->killed)
1944                      DBUG_PRINT("debug_sync_exec",
1945                                 ("killed %d from '%s'  at: '%s'",
1946                                  thd->killed, sig_wait, dsp_name));
1947                    else
1948                      DBUG_PRINT("debug_sync_exec",
1949                                 ("%s from '%s'  at: '%s'",
1950                                  error ? "timeout" : "resume",
1951                                  sig_wait, dsp_name)););
1952 
1953       /*
1954         We don't use enter_cond()/exit_cond(). They do not save old
1955         mutex and cond. This would prohibit the use of DEBUG_SYNC
1956         between other places of enter_cond() and exit_cond(). The
1957         protected mutex must always unlocked _before_ mysys_var->mutex
1958         is locked. (See comment in THD::exit_cond().)
1959       */
1960       mysql_mutex_unlock(&debug_sync_global.ds_mutex);
1961       if (old_mutex)
1962       {
1963         mysql_mutex_lock(&thd->mysys_var->mutex);
1964         thd->mysys_var->current_mutex= old_mutex;
1965         thd->mysys_var->current_cond= old_cond;
1966         thd_proc_info(thd, old_proc_info);
1967         mysql_mutex_unlock(&thd->mysys_var->mutex);
1968       }
1969       else
1970         thd_proc_info(thd, old_proc_info);
1971     }
1972     else
1973     {
1974       /* In case we don't wait, we just release the mutex. */
1975       mysql_mutex_unlock(&debug_sync_global.ds_mutex);
1976     } /* end if (action->wait_for.length()) */
1977 
1978   } /* end if (action->execute) */
1979 
1980   /* hit_limit is zero for infinite. Don't decrement unconditionally. */
1981   if (action->hit_limit)
1982   {
1983     if (!--action->hit_limit)
1984     {
1985       thd->killed= THD::KILL_QUERY;
1986       my_error(ER_DEBUG_SYNC_HIT_LIMIT, MYF(0));
1987     }
1988     DBUG_PRINT("debug_sync_exec", ("hit_limit: %lu  at: '%s'",
1989                                    action->hit_limit, dsp_name));
1990   }
1991 
1992   DBUG_VOID_RETURN;
1993 }
1994 
1995 
1996 /**
1997   Execute requested action at a synchronization point.
1998 
1999   @param[in]     thd                thread handle
2000   @param[in]     sync_point_name    name of synchronization point
2001   @param[in]     name_len           length of sync point name
2002 */
2003 
debug_sync(THD * thd,const char * sync_point_name,size_t name_len)2004 void debug_sync(THD *thd, const char *sync_point_name, size_t name_len)
2005 {
2006   if(!thd)
2007   {
2008     return;
2009   }
2010 
2011   st_debug_sync_control *ds_control= thd->debug_sync_control;
2012   st_debug_sync_action  *action;
2013   DBUG_ENTER("debug_sync");
2014   DBUG_ASSERT(thd);
2015   DBUG_ASSERT(sync_point_name);
2016   DBUG_ASSERT(name_len);
2017   DBUG_ASSERT(ds_control);
2018   DBUG_PRINT("debug_sync_point", ("hit: '%s'", sync_point_name));
2019 
2020   /* Statistics. */
2021   ds_control->dsp_hits++;
2022 
2023   if (ds_control->ds_active &&
2024       (action= debug_sync_find(ds_control->ds_action, ds_control->ds_active,
2025                                sync_point_name, name_len)) &&
2026       action->activation_count)
2027   {
2028     /* Sync point is active (action exists). */
2029     debug_sync_execute(thd, action);
2030 
2031     /* Statistics. */
2032     ds_control->dsp_executed++;
2033 
2034     /* If action became inactive, remove it to shrink the search array. */
2035     if (!action->activation_count)
2036       debug_sync_remove_action(ds_control, action);
2037   }
2038 
2039   DBUG_VOID_RETURN;
2040 }
2041 
2042 /**
2043   Define debug sync action.
2044 
2045   @param[in]        thd             thread handle
2046   @param[in]        action_str      action string
2047 
2048   @return           status
2049     @retval         FALSE           ok
2050     @retval         TRUE            error
2051 
2052   @description
2053     The function is similar to @c debug_sync_eval_action but is
2054     to be called immediately from the server code rather than
2055     to be triggered by setting a value to DEBUG_SYNC system variable.
2056 
2057   @note
2058     The input string is copied prior to be fed to
2059     @c debug_sync_eval_action to let the latter modify it.
2060 
2061     Caution.
2062     The function allocates in THD::mem_root and therefore
2063     is not recommended to be deployed inside big loops.
2064 */
2065 
debug_sync_set_action(THD * thd,const char * action_str,size_t len)2066 bool debug_sync_set_action(THD *thd, const char *action_str, size_t len)
2067 {
2068   bool                  rc;
2069   char *value;
2070   DBUG_ENTER("debug_sync_set_action");
2071   DBUG_ASSERT(thd);
2072   DBUG_ASSERT(action_str);
2073 
2074   value= strmake_root(thd->mem_root, action_str, len);
2075   rc= debug_sync_eval_action(thd, value);
2076   DBUG_RETURN(rc);
2077 }
2078 
2079 
2080 #endif /* defined(ENABLED_DEBUG_SYNC) */
2081