1 /* Copyright (c) 2016, 2020, 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
21   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 /**
24   @file storage/perfschema/pfs_data_lock.cc
25   The performance schema implementation for data locks.
26 */
27 
28 #include "storage/perfschema/pfs_data_lock.h"
29 
30 #include <stddef.h>
31 
32 #include "my_dbug.h"
33 
34 /* clang-format off */
35 /**
36   @page PAGE_PFS_DATA_LOCKS Performance schema data locks
37 
38   @section SERVER_ENGINE_INTERFACE Server / Storage engine interface
39 
40   @subsection SE_INTERFACE_REGISTRATION Registration
41 
42   @startuml
43 
44   title Registration
45 
46   participant server as "MySQL server"
47   participant pfs as "Performance schema"
48   participant se as "Storage Engine"
49   participant se_inspector as "Storage Engine\nData Lock Inspector"
50 
51   == plugin init ==
52   server -> se : plugin_init()
53   se -> pfs : register_data_lock()
54 
55   == SELECT * FROM performance_schema.data_locks ==
56   server -> pfs : table_data_locks::rnd_next()
57   pfs -> se_inspector : (multiple calls)
58 
59   == plugin deinit ==
60   server -> se : plugin_deinit()
61   se -> pfs : unregister_data_lock()
62 
63   @enduml
64 
65   To expose DATA_LOCKS to the performance schema,
66   a storage engine needs to:
67   - implement a sub class of #PSI_engine_data_lock_inspector
68   - register it with the performance schema on init
69   - unregister it with the performance schema on deinit
70 
71   While the storage engine is in use (between init and deinit),
72   the performance schema keeps a reference to the data lock inspector given,
73   and use it to inspect the storage engine data locks.
74 
75   @subsection SE_INTERFACE_SCAN_1 Iteration for each storage engine
76 
77   @startuml
78 
79   title Iteration for each storage engine
80 
81   participant server as "MySQL server"
82   participant pfs as "Performance schema\nTable data_locks"
83   participant pfs_container as "Performance schema\nData Lock container"
84   participant se_inspector as "Storage Engine\nData Lock Inspector"
85   participant se_iterator as "Storage Engine\nData Lock Iterator"
86 
87   == SELECT init ==
88   server -> pfs : rnd_init()
89   activate pfs_container
90   pfs -> pfs_container : create()
91 
92   == For each storage engine ==
93   pfs -> se_inspector : create_iterator()
94   activate se_iterator
95   se_inspector -> se_iterator : create()
96 
97   pfs -> se_iterator : (multiple calls)
98 
99   pfs -> se_iterator : destroy()
100   deactivate se_iterator
101 
102   == SELECT end ==
103   server -> pfs : rnd_end()
104   pfs -> pfs_container : destroy()
105   deactivate pfs_container
106 
107   @enduml
108 
109   When the server performs a SELECT * from performance_schema.data_locks,
110   the performance schema creates a #PSI_server_data_lock_container for
111   the duration of the table scan.
112 
113   Then, the scan loops for each storage engine capable of exposing data locks
114   (that is, engines that registered a data lock inspector).
115 
116   For each engine, the inspector is called to create an iterator,
117   dedicated for this SELECT scan.
118 
119   @subsection SE_INTERFACE_SCAN_2 Iteration inside a storage engine
120 
121   @startuml
122 
123   title Iteration inside a storage engine
124 
125   participant server as "MySQL server"
126   participant pfs as "Performance schema\nTable data_locks"
127   participant pfs_container as "Performance schema\nData Lock container"
128   participant se_iterator as "Storage Engine\nData Lock Iterator"
129 
130   loop until the storage engine iterator is done
131 
132     == SELECT scan ==
133 
134     server -> pfs : rnd_next()
135 
136     == First scan, fetch N rows at once from the storage engine ==
137 
138     pfs -> se_iterator : scan()
139     se_iterator -> pfs_container : add_row() // 1
140     se_iterator -> pfs_container : add_row() // 2
141     se_iterator -> pfs_container : ...
142     se_iterator -> pfs_container : add_row() // N
143     pfs -> pfs_container : get_row(1)
144     pfs -> server : result set row 1
145 
146     == Subsequent scans, return the rows collected ==
147 
148     server -> pfs : rnd_next()
149     pfs -> pfs_container : get_row(2)
150     pfs -> server : result set row 2
151 
152     server -> pfs : rnd_next()
153     pfs -> pfs_container : get_row(...)
154     pfs -> server : result set row ...
155 
156     server -> pfs : rnd_next()
157     pfs -> pfs_container : get_row(N)
158     pfs -> server : result set row N
159 
160   end
161 
162   @enduml
163 
164   When table_data_locks::rnd_next() is first called,
165   the performance schema calls the storage engine iterator,
166   which adds N rows in the data container.
167 
168   Upon subsequent calls to table_data_locks::rnd_next(),
169   data present in the container is returned.
170   This process loops until the storage engine iterator finally reports
171   that it reached the end of the scan.
172 
173   Note that the storage engine iterator has freedom to implement:
174   - either a full table scan, returning all rows in a single call,
175   - or a restartable scan, returning only a few rows in each call.
176 
177   The major benefit of this interface is that the engine iterator
178   can stop and restart a scan at natural boundaries within the
179   storage engine (say, return all the locks for one transaction per call),
180   which simplifies a lot the storage engine implementation.
181 */
182 /* clang-format on */
183 
PFS_data_cache()184 PFS_data_cache::PFS_data_cache() {}
185 
~PFS_data_cache()186 PFS_data_cache::~PFS_data_cache() {}
187 
cache_data(const char * ptr,size_t length)188 const char *PFS_data_cache::cache_data(const char *ptr, size_t length) {
189   /*
190     std::string is just a sequence of bytes,
191     which actually can contain a 0 byte ...
192     Never use strlen() on the binary data.
193   */
194   std::string key(ptr, length);
195   std::pair<set_type::iterator, bool> ret;
196 
197   ret = m_set.insert(key);
198   return (*ret.first).data();
199 }
200 
clear()201 void PFS_data_cache::clear() { m_set.clear(); }
202 
PFS_data_lock_container()203 PFS_data_lock_container::PFS_data_lock_container()
204     : m_logical_row_index(0), m_filter(nullptr) {}
205 
~PFS_data_lock_container()206 PFS_data_lock_container::~PFS_data_lock_container() {}
207 
cache_string(const char * string)208 const char *PFS_data_lock_container::cache_string(const char *string) {
209   return m_cache.cache_data(string, strlen(string));
210 }
211 
cache_data(const char * ptr,size_t length)212 const char *PFS_data_lock_container::cache_data(const char *ptr,
213                                                 size_t length) {
214   return m_cache.cache_data(ptr, length);
215 }
216 
accept_engine(const char * engine,size_t engine_length)217 bool PFS_data_lock_container::accept_engine(const char *engine,
218                                             size_t engine_length) {
219   if (m_filter != nullptr) {
220     return m_filter->match_engine(engine, engine_length);
221   }
222   return true;
223 }
224 
accept_lock_id(const char * engine_lock_id,size_t engine_lock_id_length)225 bool PFS_data_lock_container::accept_lock_id(const char *engine_lock_id,
226                                              size_t engine_lock_id_length) {
227   if (m_filter != nullptr) {
228     return m_filter->match_lock_id(engine_lock_id, engine_lock_id_length);
229   }
230   return true;
231 }
232 
accept_transaction_id(ulonglong transaction_id)233 bool PFS_data_lock_container::accept_transaction_id(ulonglong transaction_id) {
234   if (m_filter != nullptr) {
235     return m_filter->match_transaction_id(transaction_id);
236   }
237   return true;
238 }
239 
accept_thread_id_event_id(ulonglong thread_id,ulonglong event_id)240 bool PFS_data_lock_container::accept_thread_id_event_id(ulonglong thread_id,
241                                                         ulonglong event_id) {
242   if (m_filter != nullptr) {
243     return m_filter->match_thread_id_event_id(thread_id, event_id);
244   }
245   return true;
246 }
247 
accept_object(const char * table_schema,size_t table_schema_length,const char * table_name,size_t table_name_length,const char * partition_name,size_t partition_name_length,const char * sub_partition_name,size_t sub_partition_name_length)248 bool PFS_data_lock_container::accept_object(
249     const char *table_schema, size_t table_schema_length,
250     const char *table_name, size_t table_name_length,
251     const char *partition_name, size_t partition_name_length,
252     const char *sub_partition_name, size_t sub_partition_name_length) {
253   if (m_filter != nullptr) {
254     return m_filter->match_object(table_schema, table_schema_length, table_name,
255                                   table_name_length, partition_name,
256                                   partition_name_length, sub_partition_name,
257                                   sub_partition_name_length);
258   }
259   return true;
260 }
261 
add_lock_row(const char * engine,size_t engine_length MY_ATTRIBUTE ((unused)),const char * engine_lock_id,size_t engine_lock_id_length,ulonglong transaction_id,ulonglong thread_id,ulonglong event_id,const char * table_schema,size_t table_schema_length,const char * table_name,size_t table_name_length,const char * partition_name,size_t partition_name_length,const char * sub_partition_name,size_t sub_partition_name_length,const char * index_name,size_t index_name_length,const void * identity,const char * lock_mode,const char * lock_type,const char * lock_status,const char * lock_data)262 void PFS_data_lock_container::add_lock_row(
263     const char *engine, size_t engine_length MY_ATTRIBUTE((unused)),
264     const char *engine_lock_id, size_t engine_lock_id_length,
265     ulonglong transaction_id, ulonglong thread_id, ulonglong event_id,
266     const char *table_schema, size_t table_schema_length,
267     const char *table_name, size_t table_name_length,
268     const char *partition_name, size_t partition_name_length,
269     const char *sub_partition_name, size_t sub_partition_name_length,
270     const char *index_name, size_t index_name_length, const void *identity,
271     const char *lock_mode, const char *lock_type, const char *lock_status,
272     const char *lock_data) {
273   row_data_lock row;
274 
275   row.m_engine = engine;
276 
277   if (engine_lock_id != nullptr) {
278     size_t len = engine_lock_id_length;
279     if (len > sizeof(row.m_hidden_pk.m_engine_lock_id)) {
280       DBUG_ASSERT(false);
281       len = sizeof(row.m_hidden_pk.m_engine_lock_id);
282     }
283     if (len > 0) {
284       memcpy(row.m_hidden_pk.m_engine_lock_id, engine_lock_id, len);
285     }
286     row.m_hidden_pk.m_engine_lock_id_length = len;
287   } else {
288     row.m_hidden_pk.m_engine_lock_id_length = 0;
289   }
290 
291   row.m_transaction_id = transaction_id;
292   row.m_thread_id = thread_id;
293   row.m_event_id = event_id;
294 
295   row.m_index_row.m_object_row.m_object_type = OBJECT_TYPE_TABLE;
296 
297   if (table_schema_length > 0) {
298     memcpy(row.m_index_row.m_object_row.m_schema_name, table_schema,
299            table_schema_length);
300   }
301   row.m_index_row.m_object_row.m_schema_name_length = table_schema_length;
302 
303   if (table_name_length > 0) {
304     memcpy(row.m_index_row.m_object_row.m_object_name, table_name,
305            table_name_length);
306   }
307   row.m_index_row.m_object_row.m_object_name_length = table_name_length;
308 
309   row.m_partition_name = partition_name;
310   row.m_partition_name_length = partition_name_length;
311 
312   row.m_sub_partition_name = sub_partition_name;
313   row.m_sub_partition_name_length = sub_partition_name_length;
314 
315   if (index_name_length > 0) {
316     memcpy(row.m_index_row.m_index_name, index_name, index_name_length);
317   }
318   row.m_index_row.m_index_name_length = index_name_length;
319 
320   row.m_identity = identity;
321   row.m_lock_mode = lock_mode;
322   row.m_lock_type = lock_type;
323   row.m_lock_status = lock_status;
324   row.m_lock_data = lock_data;
325 
326   m_rows.push_back(row);
327 }
328 
clear()329 void PFS_data_lock_container::clear() {
330   m_logical_row_index = 0;
331   m_rows.clear();
332   m_cache.clear();
333 }
334 
shrink()335 void PFS_data_lock_container::shrink() {
336   /* Keep rows numbering. */
337   m_logical_row_index += m_rows.size();
338   /* Discard existing data. */
339   m_rows.clear();
340   m_cache.clear();
341 }
342 
get_row(size_t index)343 row_data_lock *PFS_data_lock_container::get_row(size_t index) {
344   if (index < m_logical_row_index) {
345     /*
346       This row existed, before a call to ::shrink().
347       The caller should not ask for it again.
348     */
349     DBUG_ASSERT(false);
350     return nullptr;
351   }
352 
353   size_t physical_index = index - m_logical_row_index;
354 
355   if (physical_index < m_rows.size()) {
356     return &m_rows[physical_index];
357   }
358 
359   return nullptr;
360 }
361 
PFS_data_lock_wait_container()362 PFS_data_lock_wait_container::PFS_data_lock_wait_container()
363     : m_logical_row_index(0), m_filter(nullptr) {}
364 
~PFS_data_lock_wait_container()365 PFS_data_lock_wait_container::~PFS_data_lock_wait_container() {}
366 
cache_string(const char * string)367 const char *PFS_data_lock_wait_container::cache_string(const char *string) {
368   return m_cache.cache_data(string, strlen(string));
369 }
370 
cache_data(const char * ptr,size_t length)371 const char *PFS_data_lock_wait_container::cache_data(const char *ptr,
372                                                      size_t length) {
373   return m_cache.cache_data(ptr, length);
374 }
375 
accept_engine(const char * engine,size_t engine_length)376 bool PFS_data_lock_wait_container::accept_engine(const char *engine,
377                                                  size_t engine_length) {
378   if (m_filter != nullptr) {
379     return m_filter->match_engine(engine, engine_length);
380   }
381   return true;
382 }
383 
accept_requesting_lock_id(const char * engine_lock_id,size_t engine_lock_id_length)384 bool PFS_data_lock_wait_container::accept_requesting_lock_id(
385     const char *engine_lock_id, size_t engine_lock_id_length) {
386   if (m_filter != nullptr) {
387     return m_filter->match_requesting_lock_id(engine_lock_id,
388                                               engine_lock_id_length);
389   }
390   return true;
391 }
392 
accept_blocking_lock_id(const char * engine_lock_id,size_t engine_lock_id_length)393 bool PFS_data_lock_wait_container::accept_blocking_lock_id(
394     const char *engine_lock_id, size_t engine_lock_id_length) {
395   if (m_filter != nullptr) {
396     return m_filter->match_blocking_lock_id(engine_lock_id,
397                                             engine_lock_id_length);
398   }
399   return true;
400 }
401 
accept_requesting_transaction_id(ulonglong transaction_id)402 bool PFS_data_lock_wait_container::accept_requesting_transaction_id(
403     ulonglong transaction_id) {
404   if (m_filter != nullptr) {
405     return m_filter->match_requesting_transaction_id(transaction_id);
406   }
407   return true;
408 }
409 
accept_blocking_transaction_id(ulonglong transaction_id)410 bool PFS_data_lock_wait_container::accept_blocking_transaction_id(
411     ulonglong transaction_id) {
412   if (m_filter != nullptr) {
413     return m_filter->match_blocking_transaction_id(transaction_id);
414   }
415   return true;
416 }
417 
accept_requesting_thread_id_event_id(ulonglong thread_id,ulonglong event_id)418 bool PFS_data_lock_wait_container::accept_requesting_thread_id_event_id(
419     ulonglong thread_id, ulonglong event_id) {
420   if (m_filter != nullptr) {
421     return m_filter->match_requesting_thread_id_event_id(thread_id, event_id);
422   }
423   return true;
424 }
425 
accept_blocking_thread_id_event_id(ulonglong thread_id,ulonglong event_id)426 bool PFS_data_lock_wait_container::accept_blocking_thread_id_event_id(
427     ulonglong thread_id, ulonglong event_id) {
428   if (m_filter != nullptr) {
429     return m_filter->match_blocking_thread_id_event_id(thread_id, event_id);
430   }
431   return true;
432 }
433 
add_lock_wait_row(const char * engine,size_t engine_length MY_ATTRIBUTE ((unused)),const char * requesting_engine_lock_id,size_t requesting_engine_lock_id_length,ulonglong requesting_transaction_id,ulonglong requesting_thread_id,ulonglong requesting_event_id,const void * requesting_identity,const char * blocking_engine_lock_id,size_t blocking_engine_lock_id_length,ulonglong blocking_transaction_id,ulonglong blocking_thread_id,ulonglong blocking_event_id,const void * blocking_identity)434 void PFS_data_lock_wait_container::add_lock_wait_row(
435     const char *engine, size_t engine_length MY_ATTRIBUTE((unused)),
436     const char *requesting_engine_lock_id,
437     size_t requesting_engine_lock_id_length,
438     ulonglong requesting_transaction_id, ulonglong requesting_thread_id,
439     ulonglong requesting_event_id, const void *requesting_identity,
440     const char *blocking_engine_lock_id, size_t blocking_engine_lock_id_length,
441     ulonglong blocking_transaction_id, ulonglong blocking_thread_id,
442     ulonglong blocking_event_id, const void *blocking_identity) {
443   row_data_lock_wait row;
444 
445   row.m_engine = engine;
446 
447   if (requesting_engine_lock_id != nullptr) {
448     size_t len = requesting_engine_lock_id_length;
449     if (len > sizeof(row.m_hidden_pk.m_requesting_engine_lock_id)) {
450       DBUG_ASSERT(false);
451       len = sizeof(row.m_hidden_pk.m_requesting_engine_lock_id);
452     }
453     if (len > 0) {
454       memcpy(row.m_hidden_pk.m_requesting_engine_lock_id,
455              requesting_engine_lock_id, len);
456     }
457     row.m_hidden_pk.m_requesting_engine_lock_id_length = len;
458   } else {
459     row.m_hidden_pk.m_requesting_engine_lock_id_length = 0;
460   }
461 
462   row.m_requesting_transaction_id = requesting_transaction_id;
463   row.m_requesting_thread_id = requesting_thread_id;
464   row.m_requesting_event_id = requesting_event_id;
465   row.m_requesting_identity = requesting_identity;
466 
467   if (blocking_engine_lock_id != nullptr) {
468     size_t len = blocking_engine_lock_id_length;
469     if (len > sizeof(row.m_hidden_pk.m_blocking_engine_lock_id)) {
470       DBUG_ASSERT(false);
471       len = sizeof(row.m_hidden_pk.m_blocking_engine_lock_id);
472     }
473     if (len > 0) {
474       memcpy(row.m_hidden_pk.m_blocking_engine_lock_id, blocking_engine_lock_id,
475              len);
476     }
477     row.m_hidden_pk.m_blocking_engine_lock_id_length = len;
478   } else {
479     row.m_hidden_pk.m_blocking_engine_lock_id_length = 0;
480   }
481 
482   row.m_blocking_transaction_id = blocking_transaction_id;
483   row.m_blocking_thread_id = blocking_thread_id;
484   row.m_blocking_event_id = blocking_event_id;
485   row.m_blocking_identity = blocking_identity;
486 
487   m_rows.push_back(row);
488 }
489 
clear()490 void PFS_data_lock_wait_container::clear() {
491   m_logical_row_index = 0;
492   m_rows.clear();
493   m_cache.clear();
494 }
495 
shrink()496 void PFS_data_lock_wait_container::shrink() {
497   /* Keep rows numbering. */
498   m_logical_row_index += m_rows.size();
499   /* Discard existing data. */
500   m_rows.clear();
501   m_cache.clear();
502 }
503 
get_row(size_t index)504 row_data_lock_wait *PFS_data_lock_wait_container::get_row(size_t index) {
505   if (index < m_logical_row_index) {
506     /*
507       This row existed, before a call to ::shrink().
508       The caller should not ask for it again.
509     */
510     DBUG_ASSERT(false);
511     return nullptr;
512   }
513 
514   size_t physical_index = index - m_logical_row_index;
515 
516   if (physical_index < m_rows.size()) {
517     return &m_rows[physical_index];
518   }
519 
520   return nullptr;
521 }
522