1 /*
2    Copyright (c) 2011, 2021, Oracle and/or its affiliates.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 #include <my_global.h>
26 #include <mysql/plugin.h>
27 #include <ndbapi/NdbApi.hpp>
28 #include <portlib/NdbTick.h>
29 #include <my_sys.h>               // my_sleep.h
30 
31 /* perform random sleep in the range milli_sleep to 2*milli_sleep */
32 static inline
do_retry_sleep(unsigned milli_sleep)33 void do_retry_sleep(unsigned milli_sleep)
34 {
35   my_sleep(1000*(milli_sleep + 5*(rand()%(milli_sleep/5))));
36 }
37 
38 
39 #include "ndb_table_guard.h"
40 
41 /*
42   The lock/unlock functions use the BACKUP_SEQUENCE row in SYSTAB_0
43 
44   retry_time == 0 means no retry
45   retry_time <  0 means infinite retries
46   retry_time >  0 means retries for max 'retry_time' seconds
47 */
48 static NdbTransaction *
gsl_lock_ext(THD * thd,Ndb * ndb,NdbError & ndb_error,int retry_time=10)49 gsl_lock_ext(THD *thd, Ndb *ndb, NdbError &ndb_error,
50              int retry_time= 10)
51 {
52   ndb->setDatabaseName("sys");
53   ndb->setDatabaseSchemaName("def");
54   NdbDictionary::Dictionary *dict= ndb->getDictionary();
55   Ndb_table_guard ndbtab_g(dict, "SYSTAB_0");
56   const NdbDictionary::Table *ndbtab= NULL;
57   NdbOperation *op;
58   NdbTransaction *trans= NULL;
59   int retry_sleep= 50; /* 50 milliseconds, transaction */
60   NDB_TICKS start;
61 
62   if (retry_time > 0)
63   {
64     start = NdbTick_getCurrentTicks();
65   }
66   while (1)
67   {
68     if (!ndbtab)
69     {
70       if (!(ndbtab= ndbtab_g.get_table()))
71       {
72         if (dict->getNdbError().status == NdbError::TemporaryError)
73           goto retry;
74         ndb_error= dict->getNdbError();
75         goto error_handler;
76       }
77     }
78 
79     trans= ndb->startTransaction();
80     if (trans == NULL)
81     {
82       ndb_error= ndb->getNdbError();
83       goto error_handler;
84     }
85 
86     op= trans->getNdbOperation(ndbtab);
87     op->readTuple(NdbOperation::LM_Exclusive);
88     op->equal("SYSKEY_0", NDB_BACKUP_SEQUENCE);
89 
90     if (trans->execute(NdbTransaction::NoCommit) == 0)
91       break;
92 
93     if (trans->getNdbError().status != NdbError::TemporaryError)
94       goto error_handler;
95     else if (thd_killed(thd))
96       goto error_handler;
97   retry:
98     if (retry_time == 0)
99       goto error_handler;
100     if (retry_time > 0)
101     {
102       const NDB_TICKS now = NdbTick_getCurrentTicks();
103       if (NdbTick_Elapsed(start,now).seconds() > (Uint64)retry_time)
104         goto error_handler;
105     }
106     if (trans)
107     {
108       ndb->closeTransaction(trans);
109       trans= NULL;
110     }
111     do_retry_sleep(retry_sleep);
112   }
113   return trans;
114 
115  error_handler:
116   if (trans)
117   {
118     ndb_error= trans->getNdbError();
119     ndb->closeTransaction(trans);
120   }
121   return NULL;
122 }
123 
124 
125 static bool
gsl_unlock_ext(Ndb * ndb,NdbTransaction * trans,NdbError & ndb_error)126 gsl_unlock_ext(Ndb *ndb, NdbTransaction *trans,
127                NdbError &ndb_error)
128 {
129   if (trans->execute(NdbTransaction::Commit))
130   {
131     ndb_error= trans->getNdbError();
132     ndb->closeTransaction(trans);
133     return false;
134   }
135   ndb->closeTransaction(trans);
136   return true;
137 }
138 
139 /*
140   lock/unlock calls are reference counted, so calls to lock
141   must be matched to a call to unlock even if the lock call fails
142 */
143 static int gsl_is_locked_or_queued= 0;
144 static int gsl_no_locking_allowed= 0;
145 static native_mutex_t gsl_mutex;
146 
147 /*
148   Indicates if ndb_global_schema_lock module is active/initialized, normally
149   turned on/off in ndbcluster_init/deinit with LOCK_plugin held.
150 */
151 static bool gsl_initialized= false;
152 
153 // NOTE! 'thd_proc_info' is defined in myql/plugin.h but not implemented, only
154 // a #define available in sql_class.h -> include sql_class.h until
155 // bug#11844974 has been fixed.
156 #include <sql_class.h>
157 
158 class Thd_proc_info_guard
159 {
160 public:
Thd_proc_info_guard(THD * thd)161   Thd_proc_info_guard(THD *thd)
162    : m_thd(thd), m_proc_info(NULL) {}
set(const char * message)163   void set(const char* message)
164   {
165     const char* old= thd_proc_info(m_thd, message);
166     if (!m_proc_info)
167     {
168       // Save the original on first change
169       m_proc_info = old;
170     }
171   }
~Thd_proc_info_guard()172   ~Thd_proc_info_guard()
173   {
174     if (m_proc_info)
175       thd_proc_info(m_thd, m_proc_info);
176   }
177 private:
178   THD *m_thd;
179   const char *m_proc_info;
180 };
181 
182 
183 #include "ndb_thd.h"
184 #include "ndb_thd_ndb.h"
185 #include "log.h"
186 
187 
188 extern ulong opt_ndb_extra_logging;
189 
190 static
191 int
ndbcluster_global_schema_lock(THD * thd,bool no_lock_queue,bool report_cluster_disconnected)192 ndbcluster_global_schema_lock(THD *thd, bool no_lock_queue,
193                               bool report_cluster_disconnected)
194 {
195   if (!gsl_initialized)
196     return 0;
197 
198   Ndb *ndb= check_ndb_in_thd(thd);
199   Thd_ndb *thd_ndb= get_thd_ndb(thd);
200   NdbError ndb_error;
201   if (thd_ndb->options & TNO_NO_LOCK_SCHEMA_OP)
202     return 0;
203   DBUG_ENTER("ndbcluster_global_schema_lock");
204   DBUG_PRINT("enter", ("query: '%-.4096s', no_lock_queue: %d",
205                        thd_query_unsafe(thd).str, no_lock_queue));
206   if (thd_ndb->global_schema_lock_count)
207   {
208     if (thd_ndb->global_schema_lock_trans)
209       thd_ndb->global_schema_lock_trans->refresh();
210     else
211       assert(thd_ndb->global_schema_lock_error != 0);
212     thd_ndb->global_schema_lock_count++;
213     DBUG_PRINT("exit", ("global_schema_lock_count: %d",
214                         thd_ndb->global_schema_lock_count));
215     DBUG_RETURN(0);
216   }
217   assert(thd_ndb->global_schema_lock_count == 0);
218   thd_ndb->global_schema_lock_count= 1;
219   thd_ndb->global_schema_lock_error= 0;
220   DBUG_PRINT("exit", ("global_schema_lock_count: %d",
221                       thd_ndb->global_schema_lock_count));
222 
223   /*
224     Check that taking the lock is allowed
225     - if not allowed to enter lock queue, return if lock exists
226     - wait until allowed
227     - increase global lock count
228   */
229   Thd_proc_info_guard proc_info(thd);
230   native_mutex_lock(&gsl_mutex);
231   /* increase global lock count */
232   gsl_is_locked_or_queued++;
233   if (no_lock_queue)
234   {
235     if (gsl_is_locked_or_queued != 1)
236     {
237       /* Other thread has lock and this thread may not enter lock queue */
238       native_mutex_unlock(&gsl_mutex);
239       thd_ndb->global_schema_lock_error= -1;
240       DBUG_PRINT("exit", ("aborting as lock exists"));
241       DBUG_RETURN(-1);
242     }
243     /* Mark that no other thread may be take lock */
244     gsl_no_locking_allowed= 1;
245   }
246   else
247   {
248     while (gsl_no_locking_allowed)
249     {
250       proc_info.set("Waiting for allowed to take ndbcluster global schema lock");
251       /* Wait until locking is allowed */
252       native_mutex_unlock(&gsl_mutex);
253       do_retry_sleep(50);
254       if (thd_killed(thd))
255       {
256         thd_ndb->global_schema_lock_error= -1;
257         DBUG_RETURN(-1);
258       }
259       native_mutex_lock(&gsl_mutex);
260     }
261   }
262   native_mutex_unlock(&gsl_mutex);
263 
264   /*
265     Take the lock
266   */
267   proc_info.set("Waiting for ndbcluster global schema lock");
268   thd_ndb->global_schema_lock_trans= gsl_lock_ext(thd, ndb, ndb_error, -1);
269 
270   DBUG_EXECUTE_IF("sleep_after_global_schema_lock", my_sleep(6000000););
271 
272   if (no_lock_queue)
273   {
274     native_mutex_lock(&gsl_mutex);
275     /* Mark that other thread may be take lock */
276     gsl_no_locking_allowed= 0;
277     native_mutex_unlock(&gsl_mutex);
278   }
279 
280   if (thd_ndb->global_schema_lock_trans)
281   {
282     if (opt_ndb_extra_logging > 19)
283     {
284       sql_print_information("NDB: Global schema lock acquired");
285     }
286 
287     // Count number of global schema locks taken by this thread
288     thd_ndb->schema_locks_count++;
289     DBUG_PRINT("info", ("schema_locks_count: %d",
290                         thd_ndb->schema_locks_count));
291 
292     DBUG_RETURN(0);
293   }
294 
295   if (ndb_error.code != 4009 || report_cluster_disconnected)
296   {
297     sql_print_warning("NDB: Could not acquire global schema lock (%d)%s",
298                       ndb_error.code, ndb_error.message);
299     push_warning_printf(thd, Sql_condition::SL_WARNING,
300                         ER_GET_ERRMSG, ER_DEFAULT(ER_GET_ERRMSG),
301                         ndb_error.code, ndb_error.message,
302                         "NDB. Could not acquire global schema lock");
303   }
304   thd_ndb->global_schema_lock_error= ndb_error.code ? ndb_error.code : -1;
305   DBUG_RETURN(-1);
306 }
307 
308 
309 static
310 int
ndbcluster_global_schema_unlock(THD * thd)311 ndbcluster_global_schema_unlock(THD *thd)
312 {
313   if (!gsl_initialized)
314     return 0;
315 
316   Thd_ndb *thd_ndb= get_thd_ndb(thd);
317   assert(thd_ndb != 0);
318   if (thd_ndb == 0 || (thd_ndb->options & TNO_NO_LOCK_SCHEMA_OP))
319     return 0;
320   Ndb *ndb= thd_ndb->ndb;
321   DBUG_ENTER("ndbcluster_global_schema_unlock");
322   NdbTransaction *trans= thd_ndb->global_schema_lock_trans;
323   thd_ndb->global_schema_lock_count--;
324   DBUG_PRINT("exit", ("global_schema_lock_count: %d",
325                       thd_ndb->global_schema_lock_count));
326   assert(ndb != NULL);
327   if (ndb == NULL)
328   {
329     DBUG_RETURN(0);
330   }
331   assert(trans != NULL || thd_ndb->global_schema_lock_error != 0);
332   if (thd_ndb->global_schema_lock_count != 0)
333   {
334     DBUG_RETURN(0);
335   }
336   thd_ndb->global_schema_lock_error= 0;
337 
338   /*
339     Decrease global lock count
340   */
341   native_mutex_lock(&gsl_mutex);
342   gsl_is_locked_or_queued--;
343   native_mutex_unlock(&gsl_mutex);
344 
345   if (trans)
346   {
347     thd_ndb->global_schema_lock_trans= NULL;
348     NdbError ndb_error;
349     if (!gsl_unlock_ext(ndb, trans, ndb_error))
350     {
351       sql_print_warning("NDB: Releasing global schema lock (%d)%s",
352                         ndb_error.code, ndb_error.message);
353       push_warning_printf(thd, Sql_condition::SL_WARNING,
354                           ER_GET_ERRMSG, ER_DEFAULT(ER_GET_ERRMSG),
355                           ndb_error.code,
356                           ndb_error.message,
357                           "ndb. Releasing global schema lock");
358       DBUG_RETURN(-1);
359     }
360     if (opt_ndb_extra_logging > 19)
361     {
362       sql_print_information("NDB: Global schema lock release");
363     }
364   }
365   DBUG_RETURN(0);
366 }
367 
368 
369 #ifndef NDB_WITHOUT_GLOBAL_SCHEMA_LOCK
370 static
371 int
ndbcluster_global_schema_func(THD * thd,bool lock,void * args)372 ndbcluster_global_schema_func(THD *thd, bool lock, void* args)
373 {
374   if (lock)
375   {
376     bool no_lock_queue = (bool)args;
377     return ndbcluster_global_schema_lock(thd, no_lock_queue, true);
378   }
379 
380   return ndbcluster_global_schema_unlock(thd);
381 }
382 #endif
383 
384 
385 #include "ndb_global_schema_lock.h"
386 
ndbcluster_global_schema_lock_init(handlerton * hton)387 void ndbcluster_global_schema_lock_init(handlerton *hton)
388 {
389   assert(gsl_initialized == false);
390   assert(gsl_is_locked_or_queued == 0);
391   assert(gsl_no_locking_allowed == 0);
392   gsl_initialized= true;
393   native_mutex_init(&gsl_mutex, MY_MUTEX_INIT_FAST);
394 
395 #ifndef NDB_WITHOUT_GLOBAL_SCHEMA_LOCK
396   hton->global_schema_func= ndbcluster_global_schema_func;
397 #endif
398 }
399 
400 
ndbcluster_global_schema_lock_deinit(void)401 void ndbcluster_global_schema_lock_deinit(void)
402 {
403   assert(gsl_initialized == true);
404   assert(gsl_is_locked_or_queued == 0);
405   assert(gsl_no_locking_allowed == 0);
406   gsl_initialized= false;
407   native_mutex_destroy(&gsl_mutex);
408 }
409 
410 
411 bool
has_required_global_schema_lock(const char * func)412 Thd_ndb::has_required_global_schema_lock(const char* func)
413 {
414 #ifdef NDB_WITHOUT_GLOBAL_SCHEMA_LOCK
415   // The global schema lock hook is not installed ->
416   //  no thd has gsl
417   return true;
418 #else
419   if (global_schema_lock_error)
420   {
421     // An error occured while locking, either because
422     // no connection to cluster or another user has locked
423     // the lock -> ok, but caller should not allow to continue
424     return false;
425   }
426 
427   if (global_schema_lock_trans)
428   {
429     global_schema_lock_trans->refresh();
430     return true; // All OK
431   }
432 
433   // No attempt at taking global schema lock has been done, neither
434   // error or trans set -> programming error
435   LEX_CSTRING query= thd_query_unsafe(m_thd);
436   sql_print_error("NDB: programming error, no lock taken while running "
437                   "query '%*s' in function '%s'",
438                   (int)query.length, query.str, func);
439   abort();
440   return false;
441 #endif
442 }
443 
444 
445 #include "ndb_global_schema_lock_guard.h"
446 
Ndb_global_schema_lock_guard(THD * thd)447 Ndb_global_schema_lock_guard::Ndb_global_schema_lock_guard(THD *thd)
448   : m_thd(thd), m_locked(false)
449 {
450 }
451 
452 
~Ndb_global_schema_lock_guard()453 Ndb_global_schema_lock_guard::~Ndb_global_schema_lock_guard()
454 {
455   if (m_locked)
456     ndbcluster_global_schema_unlock(m_thd);
457 }
458 
459 
lock(bool no_lock_queue,bool report_cluster_disconnected)460 int Ndb_global_schema_lock_guard::lock(bool no_lock_queue,
461                                        bool report_cluster_disconnected)
462 {
463   /* only one lock call allowed */
464   assert(!m_locked);
465 
466   /*
467     Always set m_locked, even if lock fails. Since the
468     lock/unlock calls are reference counted, the number
469     of calls to lock and unlock need to match up.
470   */
471   m_locked= true;
472 
473   return ndbcluster_global_schema_lock(m_thd, no_lock_queue,
474                                        report_cluster_disconnected);
475 }
476