Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion storage/innobase/include/sync0arr.ic
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ static inline sync_array_t *sync_array_get_and_reserve_cell(
sync_array_t *sync_arr = nullptr;

*cell = nullptr;
/* Random start, then walk every instance once (Bug#119585). */
ulint start = default_indexer_t<>::get_rnd_index() % sync_array_size;
for (ulint i = 0; i < sync_array_size && *cell == nullptr; ++i) {
/* Although the sync_array is get in a random way currently,
we still try at most sync_array_size times, in case any
of the sync_array we get is full */
sync_arr = sync_array_get();
sync_arr = sync_wait_array[(start + i) % sync_array_size];
*cell = sync_array_reserve_cell(sync_arr, object, type, location);
}

Expand Down
4 changes: 3 additions & 1 deletion storage/innobase/include/ut0counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ struct counter_indexer_t : public generic_indexer_t<Type, N> {
size_t c = static_cast<size_t>(my_timer_cycles());

if (c != 0) {
return (c);
/* RDTSC on some CPUs advances by 20-36 (sometimes 64), so
low bits stick. XOR from bit 6 fills them. */
return c ^ (c >> 6);
} else {
/* We may go here if my_timer_cycles() returns 0,
so we have to have the plan B for the counter. */
Expand Down
1 change: 1 addition & 0 deletions unittest/gunit/innodb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ SET(TESTS
os0file
os0thread-create
srv0conc
sync0arr
sync0rw
ut0bitset
ut0crc32
Expand Down
111 changes: 111 additions & 0 deletions unittest/gunit/innodb/sync0arr-t.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* Copyright (c) 2026, Oracle and/or its affiliates.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.

This program is designed to work with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have either included with
the program or referenced in the documentation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

/* See http://code.google.com/p/googletest/wiki/Primer */

#include <gtest/gtest.h>
#include <cstdlib>
#include <vector>

#include "os0event.h"
#include "srv0srv.h"
#include "sync0arr_impl.h"
#include "sync0debug.h"
#include "sync0rw.h"

namespace innodb_sync0arr_unittest {

/** RAII wait-array cell so TearDown can close sync arrays even on ASSERT. */
struct reserved_cell {
sync_array_t *arr{nullptr};
sync_cell_t *cell{nullptr};

~reserved_cell() {
if (arr != nullptr && cell != nullptr) {
sync_array_free_cell(arr, cell);
}
}
};

struct rw_lock_holder {
rw_lock_t *lock;

rw_lock_holder() {
lock = static_cast<rw_lock_t *>(std::malloc(sizeof(rw_lock_t)));
rw_lock_create(PSI_NOT_INSTRUMENTED, lock, LATCH_ID_BUF_BLOCK_LOCK);
}

~rw_lock_holder() {
rw_lock_free(lock);
std::free(lock);
}
};

class sync0arr : public ::testing::Test {
protected:
/* innodb_sync_array_size max. One slot per instance. */
static constexpr ulint k_n_arrays = 1024;
static constexpr ulint k_n_threads = 1024;
/* Old independent sampling fails with P=((n-1)/n)^n ~ 1/e per call.
1024 successes is enough to expose that; 2/2 is not. */
static constexpr int k_trials = 1024;

void SetUp() override {
saved_sync_array_size = srv_sync_array_size;
srv_sync_array_size = k_n_arrays;
os_event_global_init();
sync_check_init(k_n_threads);
ASSERT_EQ(sync_array_size, k_n_arrays);
ASSERT_EQ(sync_wait_array[0]->n_cells, 1UL);
}

void TearDown() override {
sync_check_close();
os_event_global_destroy();
srv_sync_array_size = saved_sync_array_size;
}

ulong saved_sync_array_size{1};
};

TEST_F(sync0arr, reserve_succeeds_when_one_instance_has_space) {
/* Destroy cells before lock: declare lock first. */
rw_lock_holder lock;
std::vector<reserved_cell> filled(k_n_arrays - 1);

for (ulint i = 0; i < k_n_arrays - 1; ++i) {
filled[i].arr = sync_wait_array[i];
filled[i].cell = sync_array_reserve_cell(filled[i].arr, lock.lock,
RW_LOCK_S, UT_LOCATION_HERE);
ASSERT_NE(filled[i].cell, nullptr) << "array " << i;
}

for (int trial = 0; trial < k_trials; ++trial) {
reserved_cell extra;
extra.arr = sync_array_get_and_reserve_cell(lock.lock, RW_LOCK_S,
UT_LOCATION_HERE, &extra.cell);
ASSERT_NE(extra.cell, nullptr) << "trial " << trial;
}
}

} // namespace innodb_sync0arr_unittest
15 changes: 15 additions & 0 deletions unittest/gunit/innodb/ut0rnd-t.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,32 @@
#include <array>
#include <cstdint>
#include <random>
#include <set>
#include "unittest/gunit/benchmark.h"

#include "storage/innobase/include/mach0data.h"
#include "storage/innobase/include/univ.i"
#include "storage/innobase/include/ut0counter.h"
#include "storage/innobase/include/ut0crc32.h"
#include "storage/innobase/include/ut0rnd.h"

#include "extra/xxhash/my_xxhash.h"

namespace innodb_ut0rnd_unittest {

/* Live get_rnd_index() path. On CPUs whose RDTSC low bits stick
(strides 20-36, 64), dropping the XOR leaves few %64 buckets. */
TEST(ut0counter, get_rnd_index_covers_mod64) {
if (my_timer_cycles() == 0) {
GTEST_SKIP() << "my_timer_cycles() is 0";
}
std::set<size_t> buckets;
for (size_t i = 0; i < 4096; ++i) {
buckets.insert(counter_indexer_t<>::get_rnd_index() % 64);
}
EXPECT_EQ(buckets.size(), 64U);
}

namespace old_impl {

/* Old implementations to compare against. */
Expand Down