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