Skip to content

Commit a36bd8b

Browse files
Jusufadis Bakamovicdahlerlend
Jusufadis Bakamovic
authored andcommitted
Bug#30050452: CRASH WHEN EXECUTING "SELECT SLEEP(10)" THROUGH CURSOR (WITH THREAD-POOL)
Problem: * TempTable segfaults in certain scenarios when ThreadPool (TP) plugin is used. * Cursor-protocol only accentuates the crash condition more. Analysis: * TempTable implements two of its critical parts through the means of thread-local variables, namely its data-backend (storage for tables) and custom memory allocator. * This becomes a design and functional issue as soon as MySQL server is run with the ThreadPool (TP) plugin because presence of that plugin will change the traditional thread-handling MySQL model, which is 1-thread-per-client-connection, to the alternative model where this assumption no longer holds and code relying on such assumption automatically becomes broken. * In particular, TP plugin will not be allocating a new thread for each new connection arriving but instead it will pick a thread from pre-allocated pool of threads and assign one to the statement execution engine. * In practice, this means that it is possible that statements within one client connection are now served with a multitude of different threads (but not simultaneously), which with traditional thread-handling model, such condition was not possible (it was always the same thread). * Furthermore, cursor-protocol makes it possible that even an execution of a single statement within one client connection is served by different threads. * E.g. Cursor-protocol enables fetching the result set of a query (data) in chunks. And TP plugin makes a situation possible where each chunk is fetched from a different thread. * TL;DR thread-local-storage variables cannot really co-exist with TP plugin in their natural form. Solution: * Replace TempTable parts which are implemented in terms of thread-local-storage variables with a solution which: * Is functionally correct in both cases, with and without TP plugin running. * Does not sacrifice the overall performance at any rate. * Does not lock the design but keeps the door open for future advancements (e.g. more control over memory consumption). * Basic idea is to: * Organize data-backend (table-storage) into shards * Ditto for shared-blocks (custom memory allocator external state) * Use unique connection identifier to map between connections and their corresponding shards Addendum: * This patch also fixes some other issues which have been reported but which are directly or indirectly caused by the same limitation in design: * BUG #30050420 CRASHES WHEN CLOSING CONNECTION WITH UNCLOSED CURSOR (WITH THREAD-POOL) * BUG #31116036 TEMPTABLE WASTES 1MB FOR EACH CONNECTION IN THREAD CACHE * BUG #31471863 TEMPTABLE ENGINE USES MUCH MORE MEMORY THAN MEMORY ENGINE FOR TEMPORARY TABLE Reviewed-by: Pawel Olchawa <[email protected]> RB: 24497
1 parent 3fdef64 commit a36bd8b

22 files changed

+1408
-319
lines changed

include/my_compiler.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,4 +342,28 @@ inline bool unlikely(bool expr) { return expr; }
342342
#define MY_COMPILER_CLANG_WORKAROUND_REF_DOCBUG() \
343343
MY_COMPILER_CLANG_DIAGNOSTIC_IGNORE("-Wdocumentation")
344344

345+
/**
346+
* ignore -Wunused-variable compiler warnings for \@see \@ref
347+
*
348+
* @code
349+
* MY_COMPILER_DIAGNOSTIC_PUSH()
350+
* MY_COMPILER_CLANG_WORKAROUND_FALSE_POSITIVE_UNUSED_VARIABLE_WARNING()
351+
* ...
352+
* MY_COMPILER_DIAGNOSTIC_POP()
353+
* @endcode
354+
*
355+
* @see MY_COMPILER_DIAGNOSTIC_PUSH()
356+
* @see MY_COMPILER_DIAGNOSTIC_POP()
357+
*
358+
* allows to work around false positives -Wunused-variable warnings like:
359+
*
360+
* - \@sa \@ref
361+
* - \@see \@ref
362+
* - \@return \@ref
363+
* https://bugs.llvm.org/show_bug.cgi?id=46035
364+
*
365+
*/
366+
#define MY_COMPILER_CLANG_WORKAROUND_FALSE_POSITIVE_UNUSED_VARIABLE_WARNING() \
367+
MY_COMPILER_CLANG_DIAGNOSTIC_IGNORE("-Wunused-variable")
368+
345369
#endif /* MY_COMPILER_INCLUDED */

sql/handler.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,14 @@ size_t num_hton2plugins() { return se_plugin_array.size(); }
237237

238238
st_plugin_int *insert_hton2plugin(uint slot, st_plugin_int *plugin) {
239239
if (se_plugin_array.assign_at(slot, plugin)) return nullptr;
240+
builtin_htons.assign_at(slot, true);
240241
return se_plugin_array[slot];
241242
}
242243

243244
st_plugin_int *remove_hton2plugin(uint slot) {
244245
st_plugin_int *retval = se_plugin_array[slot];
245246
se_plugin_array[slot] = NULL;
247+
builtin_htons.assign_at(slot, false);
246248
return retval;
247249
}
248250

storage/temptable/include/temptable/allocator.h

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2016, 2019, Oracle and/or its affiliates. All Rights Reserved.
1+
/* Copyright (c) 2016, 2020, Oracle and/or its affiliates. All Rights Reserved.
22
33
This program is free software; you can redistribute it and/or modify it under
44
the terms of the GNU General Public License, version 2.0, as published by the
@@ -43,9 +43,6 @@ TempTable custom allocator. */
4343

4444
namespace temptable {
4545

46-
/** Block that is shared between different tables within a given OS thread. */
47-
extern thread_local Block shared_block;
48-
4946
/* Thin abstraction which enables logging of memory operations.
5047
*
5148
* Used by the Allocator to implement switching from RAM to MMAP-backed
@@ -214,7 +211,7 @@ class Allocator : private MemoryMonitor {
214211
};
215212

216213
/** Constructor. */
217-
Allocator();
214+
explicit Allocator(Block *shared_block);
218215

219216
/** Constructor from allocator of another type. The state is copied into the
220217
* new object. */
@@ -293,23 +290,31 @@ class Allocator : private MemoryMonitor {
293290
See AllocatorState for details.
294291
*/
295292
std::shared_ptr<AllocatorState> m_state;
293+
294+
/** A block of memory which is a state external to this allocator and can be
295+
* shared among different instances of the allocator (not simultaneously). In
296+
* order to speed up its operations, allocator may decide to consume the
297+
* memory of this shared block.
298+
*/
299+
Block *m_shared_block;
296300
};
297301

298302
/* Implementation of inlined methods. */
299303

300304
template <class T, class AllocationScheme>
301-
inline Allocator<T, AllocationScheme>::Allocator()
302-
: m_state(std::make_shared<AllocatorState>()) {}
305+
inline Allocator<T, AllocationScheme>::Allocator(Block *shared_block)
306+
: m_state(std::make_shared<AllocatorState>()),
307+
m_shared_block(shared_block) {}
303308

304309
template <class T, class AllocationScheme>
305310
template <class U>
306311
inline Allocator<T, AllocationScheme>::Allocator(const Allocator<U> &other)
307-
: m_state(other.m_state) {}
312+
: m_state(other.m_state), m_shared_block(other.m_shared_block) {}
308313

309314
template <class T, class AllocationScheme>
310315
template <class U>
311316
inline Allocator<T, AllocationScheme>::Allocator(Allocator<U> &&other) noexcept
312-
: m_state(std::move(other.m_state)) {}
317+
: m_state(std::move(other.m_state)), m_shared_block(other.m_shared_block) {}
313318

314319
template <class T, class AllocationScheme>
315320
inline Allocator<T, AllocationScheme>::~Allocator() {}
@@ -342,14 +347,15 @@ inline T *Allocator<T, AllocationScheme>::allocate(size_t n_elements) {
342347

343348
Block *block;
344349

345-
if (shared_block.is_empty()) {
346-
shared_block = Block(AllocationScheme::block_size(m_state->number_of_blocks,
347-
n_bytes_requested),
348-
Source::RAM);
349-
block = &shared_block;
350+
if (m_shared_block && m_shared_block->is_empty()) {
351+
*m_shared_block = Block(AllocationScheme::block_size(
352+
m_state->number_of_blocks, n_bytes_requested),
353+
Source::RAM);
354+
block = m_shared_block;
350355
++m_state->number_of_blocks;
351-
} else if (shared_block.can_accommodate(n_bytes_requested)) {
352-
block = &shared_block;
356+
} else if (m_shared_block &&
357+
m_shared_block->can_accommodate(n_bytes_requested)) {
358+
block = m_shared_block;
353359
} else if (m_state->current_block.is_empty() ||
354360
!m_state->current_block.can_accommodate(n_bytes_requested)) {
355361
const size_t block_size = AllocationScheme::block_size(
@@ -396,7 +402,7 @@ inline void Allocator<T, AllocationScheme>::deallocate(T *chunk_data,
396402
const auto remaining_chunks =
397403
block.deallocate(Chunk(chunk_data), n_bytes_requested);
398404
if (remaining_chunks == 0) {
399-
if (block == shared_block) {
405+
if (m_shared_block && (block == *m_shared_block)) {
400406
// Do nothing. Keep the last block alive.
401407
} else {
402408
DBUG_ASSERT(m_state->number_of_blocks > 0);

storage/temptable/include/temptable/constants.h

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (c) 2017, 2017, Oracle and/or its affiliates. All Rights Reserved.
1+
/* Copyright (c) 2017, 2020, Oracle and/or its affiliates. All Rights Reserved.
22
33
This program is free software; you can redistribute it and/or modify it under
44
the terms of the GNU General Public License, version 2.0, as published by the
@@ -26,6 +26,8 @@ TempTable constants. */
2626
#ifndef TEMPTABLE_CONSTANTS_H
2727
#define TEMPTABLE_CONSTANTS_H
2828

29+
#include "my_config.h"
30+
2931
namespace temptable {
3032

3133
/** Multiply a number by 1024.
@@ -68,6 +70,22 @@ constexpr size_t STORAGE_PAGE_SIZE = 64_KiB;
6870
/** Number of buckets to have by default in a hash index. */
6971
constexpr size_t INDEX_DEFAULT_HASH_TABLE_BUCKETS = 1024;
7072

73+
/** Store build-type information into the constexpr expression. */
74+
#ifndef DBUG_OFF
75+
constexpr bool DEBUG_BUILD = true;
76+
#else
77+
constexpr bool DEBUG_BUILD = false;
78+
#endif /* DBUG_OFF */
79+
80+
/** Store L1-dcache size information into the constexpr expression. */
81+
constexpr size_t L1_DCACHE_SIZE = CPU_LEVEL1_DCACHE_LINESIZE;
82+
83+
/** Number of shards in key-value store. */
84+
constexpr size_t KV_STORE_SHARDS_COUNT = 16 * 1024;
85+
86+
/** Size of a pool containing shared-blocks. */
87+
constexpr size_t SHARED_BLOCK_POOL_SIZE = 16 * 1024;
88+
7189
} /* namespace temptable */
7290

7391
#endif /* TEMPTABLE_CONSTANTS_H */

storage/temptable/include/temptable/handler.h

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,16 @@ Also called "key", "field", "subkey", "key part", "key segment" elsewhere.
128128
#ifndef TEMPTABLE_HANDLER_H
129129
#define TEMPTABLE_HANDLER_H
130130

131-
#ifndef DBUG_OFF
132-
#include <thread>
133-
#endif /* DBUG_OFF */
134-
135131
#include "sql/handler.h"
136132
#include "sql/table.h"
137133
#include "storage/temptable/include/temptable/storage.h"
138134
#include "storage/temptable/include/temptable/table.h"
139135

140136
namespace temptable {
141137

138+
/** Forward declarations. */
139+
class Block;
140+
142141
/** Temptable engine handler. */
143142
class Handler : public ::handler {
144143
public:
@@ -524,6 +523,10 @@ class Handler : public ::handler {
524523
/** Currently opened table, or `nullptr` if none is opened. */
525524
Table *m_opened_table;
526525

526+
/** Pointer to the non-owned shared-block of memory to be re-used by all
527+
* `Allocator` instances or copies made by `Table`. */
528+
Block *m_shared_block;
529+
527530
/** Iterator used by `rnd_init()`, `rnd_next()` and `rnd_end()` methods.
528531
* It points to the row that was retrieved by the last read call (e.g.
529532
* `rnd_next()`). */
@@ -554,17 +557,6 @@ class Handler : public ::handler {
554557

555558
/** Number of deleted rows by this handler object. */
556559
size_t m_deleted_rows;
557-
558-
#ifndef DBUG_OFF
559-
/** Check if the current OS thread is the one that created this handler
560-
* object.
561-
* @return true if current thread == creator thread */
562-
bool current_thread_is_creator() const;
563-
564-
/** The OS thread that created this handler object. The only thread that is
565-
* supposed to use it and the tables created via it. */
566-
std::thread::id m_owner;
567-
#endif /* DBUG_OFF */
568560
};
569561

570562
inline void Handler::opened_table_validate() {
@@ -588,6 +580,9 @@ inline bool Handler::is_field_type_fixed_size(const Field &mysql_field) const {
588580
}
589581
}
590582

583+
void kv_store_shards_debug_dump();
584+
void shared_block_pool_release(THD *thd);
585+
591586
} /* namespace temptable */
592587

593588
#endif /* TEMPTABLE_HANDLER_H */
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/* Copyright (c) 2020, Oracle and/or its affiliates. All Rights Reserved.
2+
3+
This program is free software; you can redistribute it and/or modify it under
4+
the terms of the GNU General Public License, version 2.0, as published by the
5+
Free Software Foundation.
6+
7+
This program is also distributed with certain software (including but not
8+
limited to OpenSSL) that is licensed under separate terms, as designated in a
9+
particular file or component or in included license documentation. The authors
10+
of MySQL hereby grant you an additional permission to link the program and
11+
your derivative works with the separately licensed software that they have
12+
included with MySQL.
13+
14+
This program is distributed in the hope that it will be useful, but WITHOUT
15+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16+
FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
17+
for more details.
18+
19+
You should have received a copy of the GNU General Public License along with
20+
this program; if not, write to the Free Software Foundation, Inc.,
21+
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22+
23+
/** @file storage/temptable/include/temptable/kv_store.h
24+
TempTable key-value store implementation. */
25+
26+
#ifndef TEMPTABLE_KV_STORE_H
27+
#define TEMPTABLE_KV_STORE_H
28+
29+
#include <shared_mutex>
30+
#include <string>
31+
#include <unordered_map>
32+
33+
#include "storage/temptable/include/temptable/constants.h"
34+
#include "storage/temptable/include/temptable/kv_store_logger.h"
35+
36+
namespace temptable {
37+
38+
/** Forward-declarations. */
39+
class Table;
40+
41+
/** Key-value store, a convenience wrapper class which models a thread-safe
42+
* dictionary type.
43+
*
44+
* Thread-safety is accomplished by using a `Lock` which can be any of the usual
45+
* mutual exclusive algorithms from C++ thread-support library or any other
46+
* which satisfy C++ concurrency named requirements. E.g. mutex, timed_mutex,
47+
* recursive_mutex, recursive_timed_mutex, shared_mutex, shared_timed_mutex, ...
48+
* User code can opt-in for any of those. Also, whether the actual
49+
* implementation will use shared-locks or exclusive-locks (for read-only
50+
* operations) is handled automagically by this class.
51+
*
52+
* Furthermore, user code can similarly opt-in for different key-value
53+
* implementation but whose interface is compatible with the one of
54+
* std::unordered_map.
55+
* */
56+
template <typename Lock,
57+
template <typename...> class KeyValueImpl = std::unordered_map>
58+
class Key_value_store
59+
: public Key_value_store_logger<Key_value_store<Lock, KeyValueImpl>,
60+
DEBUG_BUILD> {
61+
/** Do not break encapsulation when using CRTP. */
62+
friend struct Key_value_store_logger<Key_value_store<Lock, KeyValueImpl>,
63+
DEBUG_BUILD>;
64+
65+
/** Help ADL to bring debugging/logging symbols into the scope. */
66+
using Key_value_store_logger<Key_value_store<Lock, KeyValueImpl>,
67+
DEBUG_BUILD>::dbug_print;
68+
using Key_value_store_logger<Key_value_store<Lock, KeyValueImpl>,
69+
DEBUG_BUILD>::log;
70+
71+
/** Check whether we can use shared locks (which enable multiple concurrent
72+
* readers) or must we rather fallback to exclusive locks. Shared-locks will
73+
* be used only during read-only operations.
74+
* */
75+
using Exclusive_or_shared_lock =
76+
std::conditional_t<std::is_same<Lock, std::shared_timed_mutex>::value,
77+
std::shared_lock<Lock>, std::lock_guard<Lock>>;
78+
79+
/** Alias for our key-value store implementation. */
80+
using Key_value_store_impl = KeyValueImpl<std::string, Table>;
81+
82+
/** Container holding (table-name, Table) tuples. */
83+
Key_value_store_impl m_kv_store;
84+
85+
/** Lock type. */
86+
Lock m_lock;
87+
88+
public:
89+
/** Inserts a new table into the container constructed in-place with the
90+
* given args if there is no table with the key in the container.
91+
*
92+
* [in] args Arguments to forward to the constructor of the table.
93+
* @return Returns a pair consisting of an iterator to the inserted table,
94+
* or the already-existing table if no insertion happened, and a bool
95+
* denoting whether the insertion took place (true if insertion happened,
96+
* false if it did not).
97+
* */
98+
template <class... Args>
99+
std::pair<typename Key_value_store_impl::iterator, bool> emplace(
100+
Args &&... args) {
101+
std::lock_guard<Lock> lock(m_lock);
102+
dbug_print();
103+
log(Key_value_store_stats::Event::EMPLACE);
104+
return m_kv_store.emplace(args...);
105+
}
106+
107+
/** Searches for a table with given name (key).
108+
*
109+
* [in] key Name of a table to search for.
110+
* @return Pointer to table if found, nullptr otherwise.
111+
* */
112+
Table *find(const std::string &key) {
113+
Exclusive_or_shared_lock lock(m_lock);
114+
auto iter = m_kv_store.find(key);
115+
if (iter != m_kv_store.end()) {
116+
return &iter->second;
117+
} else {
118+
return nullptr;
119+
}
120+
}
121+
122+
/** Removes the table (if one exists) with the given name (key).
123+
*
124+
* [in] key Name of a table to remove..
125+
* @return Number of elements removed.
126+
* */
127+
typename Key_value_store_impl::size_type erase(const std::string &key) {
128+
std::lock_guard<Lock> lock(m_lock);
129+
dbug_print();
130+
log(Key_value_store_stats::Event::ERASE);
131+
return m_kv_store.erase(key);
132+
}
133+
};
134+
135+
} // namespace temptable
136+
137+
#endif /* TEMPTABLE_KV_STORE_H */

0 commit comments

Comments
 (0)