Skip to content

Commit d8d812c

Browse files
author
Jakub Łopuszański
committed
Post-push fix for Bug#36302624 performance_schema.data_lock_waits is generated with O(N^3) complexity
Problem: On 32-bit systems alignof(uint64_t) might be 4. This in turn permits alignof(lock_t) == 4 and sizeof(lock_t)%8 == 4. Taken together, this means that the bitmap which is immediately after the lock_t struct can have an address which is not a multiple of 8. This is suboptimal as Bitmap::find_set(..) handles such case by entering a slow path. There is a debug-only assert which checks that the address of bitmap is a multiple of 8 to ensure high performance. This assertion is failing on 32-bit builds. Solution: Added alignas(8) to definition of lock_t to enforce proper alignment. Added a static assert that alignof(lock_t) is at least 8, to prevent regression. Added a static assert that heap allocator respects alignof(lock_t). Added an assert to dict_mem_table_create that the lock_t allocated there for autoinc is also properly aligned. As sizeof(T) must be at least alignof(T), this also ensures that sizeof(lock_t) is a multiple of 8. Note: the instances of lock_t are obtained in various ways: a) By calling ut::malloc_withkey(...,REC_LOCK_SIZE * REC_LOCK_CACHE) to allocate one big chunk, and then slicing it into REC_LOCK_CACHE items of REC_LOCK_SIZE bytes. This is fine, because allocator ensures alignof(std::max_align_t) which must be at least that of double. So, the address of the big buffer is at least divisible by 8. And because REC_LOCK_SIZE is defined as sizeof(ib_lock_t) + 256, so as long as alignof(lock_t) is at least 8, then adding 256 to it will not spoil the property that each smaller buffer has an address divisible by 8. b) Similar to a) but with TABLE_LOCK_SIZE and TABLE_LOCK_CACHE. Here the difference is that TABLE_LOCK_SIZE doesn't include 256 bytes of padding for the bitmap, which makes the proof even simpler. Also we don't ever use bitmaps for table locks, so this shouldn't matter in practice. c) Calls to mem_heap_alloc in RecLock::lock_alloc, lock_table_create and dict_mem_table_create. Each of them should give an address which has at least UNIV_MEM_ALIGNMENT==8, so should be fine. There are asserts in each of these places (this patch adds the missing one) that the allocated lock object has at least alignof(lock_t). This is why we expect all the lock_t structs to have at least alignof(lock_t) and why it is sufficient to ensure alignof(lock_t) is at least 8. Change-Id: I7d4892d28baefd11aa1c0fa4ac2dba502f270bd2
1 parent 587c74a commit d8d812c

File tree

4 files changed

+29
-19
lines changed

4 files changed

+29
-19
lines changed

storage/innobase/dict/mem.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,7 @@ dict_table_t *dict_mem_table_create(const char *name, space_id_t space,
233233
dict_table_stats_lock() will not be noop. */
234234
dict_table_stats_latch_create(table, true);
235235

236-
table->autoinc_lock =
237-
static_cast<ib_lock_t *>(mem_heap_alloc(heap, lock_get_size()));
236+
table->autoinc_lock = lock_alloc_from_heap(heap);
238237

239238
/* lazy creation of table autoinc latch */
240239
dict_table_autoinc_create_lazy(table);

storage/innobase/include/lock0lock.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,19 @@ class ReadView;
248248

249249
extern bool innobase_deadlock_detect;
250250

251-
/** Gets the size of a lock struct.
252-
@return size in bytes */
253-
ulint lock_get_size(void);
251+
/** Allocates memory suitable for holding a lock_t from specified heap.
252+
The allocated memory has additional bitmap_bytes right after the returned
253+
lock_t instance for holding the bitmap used by LOCK_REC type.
254+
@param[in] heap
255+
The heap to allocate the memory from
256+
@param[in] bitmap_bytes
257+
The number of bytes to reserve right after the lock_t struct
258+
for the bitmap. Defaults to 0, which is ok for LOCK_TABLE.
259+
@return A pointer to the memory allocated from the heap, aligned as lock_t,
260+
and of size sizeof(lock_t)+bitmap_bytes. Note that it can contain garbage,
261+
so it is caller's responsibility to initialize lock_t and the bitmap. */
262+
lock_t *lock_alloc_from_heap(mem_heap_t *heap, size_t bitmap_bytes = 0);
263+
254264
/** Creates the lock system at database start. */
255265
void lock_sys_create(
256266
ulint n_cells); /*!< in: number of slots in lock hash table */

storage/innobase/include/lock0priv.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ static inline bool lock_mode_is_next_key_lock(ulint mode) {
134134
static inline bool lock_rec_get_nth_bit(const lock_t *lock, ulint i);
135135

136136
/** Lock struct; protected by lock_sys latches */
137-
struct lock_t {
137+
struct alignas(8 /* For efficient Bitmap::find_set */) lock_t {
138138
/** transaction owning the lock */
139139
trx_t *trx;
140140

@@ -256,7 +256,9 @@ struct lock_t {
256256
/** @overload */
257257
Bitset<const byte> bitset() const {
258258
ut_ad(is_record_lock());
259-
static_assert(alignof(uint64_t) <= alignof(lock_t),
259+
/* On a 32-bit system alignof(uint64_t) might be 4. Still, the
260+
Bitmap::find_set goes into slow path if address is not a multiple of 8. */
261+
static_assert(8 <= alignof(lock_t),
260262
"lock_t and thus the bitmap after lock_t should be aligned "
261263
"for efficient 64-bit access");
262264
const byte *bitmap = (const byte *)&this[1];

storage/innobase/lock/lock0lock.cc

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -474,10 +474,6 @@ void lock_sys_close(void) {
474474
lock_sys = nullptr;
475475
}
476476

477-
/** Gets the size of a lock struct.
478-
@return size in bytes */
479-
ulint lock_get_size(void) { return ((ulint)sizeof(lock_t)); }
480-
481477
bool lock_is_waiting(const lock_t &lock) {
482478
ut_ad(locksys::owns_lock_shard(&lock));
483479
return lock.is_waiting();
@@ -1108,6 +1104,15 @@ ulint lock_number_of_tables_locked(const trx_t *trx) {
11081104
return count;
11091105
}
11101106

1107+
lock_t *lock_alloc_from_heap(mem_heap_t *heap, size_t bitmap_bytes) {
1108+
const size_t n_bytes = sizeof(lock_t) + bitmap_bytes;
1109+
static_assert(alignof(lock_t) <= UNIV_MEM_ALIGNMENT,
1110+
"heap allocator must ensure lock_t is properly aligned");
1111+
auto ptr = mem_heap_alloc(heap, n_bytes);
1112+
ut_a(ut::is_aligned_as<lock_t>(ptr));
1113+
return reinterpret_cast<lock_t *>(ptr);
1114+
}
1115+
11111116
/*============== RECORD LOCK CREATION AND QUEUE MANAGEMENT =============*/
11121117

11131118
/**
@@ -1158,11 +1163,7 @@ lock_t *RecLock::lock_alloc(trx_t *trx, dict_index_t *index, ulint mode,
11581163

11591164
if (trx->lock.rec_cached >= trx->lock.rec_pool.size() ||
11601165
sizeof(*lock) + size > REC_LOCK_SIZE) {
1161-
ulint n_bytes = size + sizeof(*lock);
1162-
mem_heap_t *heap = trx->lock.lock_heap;
1163-
auto ptr = mem_heap_alloc(heap, n_bytes);
1164-
ut_a(ut::is_aligned_as<lock_t>(ptr));
1165-
lock = reinterpret_cast<lock_t *>(ptr);
1166+
lock = lock_alloc_from_heap(trx->lock.lock_heap, size);
11661167
} else {
11671168
lock = trx->lock.rec_pool[trx->lock.rec_cached];
11681169
++trx->lock.rec_cached;
@@ -3293,9 +3294,7 @@ static inline lock_t *lock_table_create(
32933294
} else if (trx->lock.table_cached < trx->lock.table_pool.size()) {
32943295
lock = trx->lock.table_pool[trx->lock.table_cached++];
32953296
} else {
3296-
auto ptr = mem_heap_alloc(trx->lock.lock_heap, sizeof(*lock));
3297-
ut_a(ut::is_aligned_as<lock_t>(ptr));
3298-
lock = static_cast<lock_t *>(ptr);
3297+
lock = lock_alloc_from_heap(trx->lock.lock_heap);
32993298
}
33003299
lock->type_mode = uint32_t(type_mode | LOCK_TABLE);
33013300
lock->trx = trx;

0 commit comments

Comments
 (0)