mariadb/storage/innobase/include/dyn0buf.h
Marko Mäkelä 24fe53477c MDEV-29603 btr_cur_open_at_index_side() is missing some consistency checks
btr_cur_t: Zero-initialize all fields in the default constructor.

btr_cur_t::index: Remove; it duplicated page_cur.index.

Many functions: Remove arguments that were duplicating
page_cur_t::index and page_cur_t::block.

page_cur_open_level(), btr_pcur_open_level(): Replaces
btr_cur_open_at_index_side() for dict_stats_analyze_index().
At the end, release all latches except the dict_index_t::lock
and the buf_page_t::lock on the requested page.

dict_stats_analyze_index(): Rely on mtr_t::rollback_to_savepoint()
to release all uninteresting page latches.

btr_search_guess_on_hash(): Simplify the logic, and invoke
mtr_t::rollback_to_savepoint().

We will use plain C++ std::vector<mtr_memo_slot_t> for mtr_t::m_memo.
In this way, we can avoid setting mtr_memo_slot_t::object to nullptr
and instead just remove garbage from m_memo.

mtr_t::rollback_to_savepoint(): Shrink the vector. We will be needing this
in dict_stats_analyze_index(), where we will release page latches and
only retain the index->lock in mtr_t::m_memo.

mtr_t::release_last_page(): Release the last acquired page latch.
Replaces btr_leaf_page_release().

mtr_t::release(const buf_block_t&): Release a single page latch.
Used in btr_pcur_move_backward_from_page().

mtr_t::memo_release(): Replaced with mtr_t::release().

mtr_t::upgrade_buffer_fix(): Acquire a latch for a buffer-fixed page.
This replaces the double bookkeeping in btr_cur_t::open_leaf().

Reviewed by: Vladislav Lesin
2022-11-17 08:19:01 +02:00

442 lines
9.4 KiB
C++

/*****************************************************************************
Copyright (c) 2013, 2016, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2018, 2022, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; version 2 of the License.
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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA
*****************************************************************************/
/**************************************************//**
@file include/dyn0buf.h
The dynamically allocated buffer implementation
Created 2013-03-16 Sunny Bains
*******************************************************/
#ifndef dyn0buf_h
#define dyn0buf_h
#include "mem0mem.h"
#include "dyn0types.h"
#include "ilist.h"
/** Class that manages dynamic buffers. It uses a UT_LIST of
mtr_buf_t::block_t instances. We don't use STL containers in
order to avoid the overhead of heap calls. Using a custom memory
allocator doesn't solve the problem either because we have to get
the memory from somewhere. We can't use the block_t::m_data as the
backend for the custom allocator because we would like the data in
the blocks to be contiguous. */
class mtr_buf_t {
public:
/** SIZE - sizeof(m_node) + sizeof(m_used) */
enum { MAX_DATA_SIZE = DYN_ARRAY_DATA_SIZE
- sizeof(ilist_node<>) + sizeof(uint32_t) };
class block_t : public ilist_node<> {
public:
block_t()
{
compile_time_assert(MAX_DATA_SIZE <= (2 << 15));
init();
}
/**
Gets the number of used bytes in a block.
@return number of bytes used */
ulint used() const
MY_ATTRIBUTE((warn_unused_result))
{
return(static_cast<ulint>(m_used & ~DYN_BLOCK_FULL_FLAG));
}
/**
Gets pointer to the start of data.
@return pointer to data */
byte* start()
MY_ATTRIBUTE((warn_unused_result))
{
return(m_data);
}
/**
@return start of data - non const version */
byte* begin()
MY_ATTRIBUTE((warn_unused_result))
{
return(m_data);
}
/**
@return end of used data - non const version */
byte* end()
MY_ATTRIBUTE((warn_unused_result))
{
return(begin() + m_used);
}
/**
@return start of data - const version */
const byte* begin() const
MY_ATTRIBUTE((warn_unused_result))
{
return(m_data);
}
/**
@return end of used data - const version */
const byte* end() const
MY_ATTRIBUTE((warn_unused_result))
{
return(begin() + m_used);
}
private:
/**
@return pointer to start of reserved space */
template <typename Type>
Type push(uint32_t size)
{
Type ptr = reinterpret_cast<Type>(end());
m_used += size;
ut_ad(m_used <= uint32_t(MAX_DATA_SIZE));
return(ptr);
}
/**
Grow the stack. */
void close(const byte* ptr)
{
/* Check that it is within bounds */
ut_ad(ptr >= begin());
ut_ad(ptr <= begin() + m_buf_end);
/* We have done the boundary check above */
m_used = uint32_t(ptr - begin());
ut_ad(m_used <= MAX_DATA_SIZE);
ut_d(m_buf_end = 0);
}
/**
Initialise the block */
void init()
{
m_used = 0;
ut_d(m_buf_end = 0);
ut_d(m_magic_n = DYN_BLOCK_MAGIC_N);
}
private:
#ifdef UNIV_DEBUG
/** If opened then this is the buffer end offset, else 0 */
ulint m_buf_end;
/** Magic number (DYN_BLOCK_MAGIC_N) */
ulint m_magic_n;
#endif /* UNIV_DEBUG */
/** Storage */
byte m_data[MAX_DATA_SIZE];
/** number of data bytes used in this block;
DYN_BLOCK_FULL_FLAG is set when the block becomes full */
uint32_t m_used;
friend class mtr_buf_t;
};
typedef sized_ilist<block_t> list_t;
/** Default constructor */
mtr_buf_t()
:
m_heap(),
m_size()
{
push_back(&m_first_block);
}
/** Destructor */
~mtr_buf_t()
{
erase();
}
/** Reset the buffer vector */
void erase()
{
if (m_heap != NULL) {
mem_heap_free(m_heap);
m_heap = NULL;
/* Initialise the list and add the first block. */
m_list.clear();
m_list.push_back(m_first_block);
} else {
m_first_block.init();
ut_ad(m_list.size() == 1);
}
m_size = 0;
}
/**
Makes room on top and returns a pointer to a buffer in it. After
copying the elements, the caller must close the buffer using close().
@param size in bytes of the buffer; MUST be <= MAX_DATA_SIZE!
@return pointer to the buffer */
byte* open(ulint size)
MY_ATTRIBUTE((warn_unused_result))
{
ut_ad(size > 0);
ut_ad(size <= MAX_DATA_SIZE);
block_t* block;
block = has_space(size) ? back() : add_block();
ut_ad(block->m_used <= MAX_DATA_SIZE);
ut_d(block->m_buf_end = block->m_used + size);
return(block->end());
}
/**
Closes the buffer returned by open.
@param ptr end of used space */
void close(const byte* ptr)
{
ut_ad(!m_list.empty());
block_t* block = back();
m_size -= block->used();
block->close(ptr);
m_size += block->used();
}
/**
Makes room on top and returns a pointer to the added element.
The caller must copy the element to the pointer returned.
@param size in bytes of the element
@return pointer to the element */
template <typename Type>
Type push(uint32_t size)
{
ut_ad(size > 0);
ut_ad(size <= MAX_DATA_SIZE);
block_t* block;
block = has_space(size) ? back() : add_block();
m_size += size;
/* See ISO C++03 14.2/4 for why "template" is required. */
return(block->template push<Type>(size));
}
/**
Pushes n bytes.
@param str string to write
@param len string length */
void push(const byte* ptr, uint32_t len)
{
while (len > 0) {
uint32_t n_copied = std::min(len,
uint32_t(MAX_DATA_SIZE));
::memmove(push<byte*>(n_copied), ptr, n_copied);
ptr += n_copied;
len -= n_copied;
}
}
/**
Returns a pointer to an element in the buffer. const version.
@param pos position of element in bytes from start
@return pointer to element */
template <typename Type>
const Type at(ulint pos) const
{
block_t* block = const_cast<block_t*>(
const_cast<mtr_buf_t*>(this)->find(pos));
return(reinterpret_cast<Type>(block->begin() + pos));
}
/**
Returns a pointer to an element in the buffer. non const version.
@param pos position of element in bytes from start
@return pointer to element */
template <typename Type>
Type at(ulint pos)
{
block_t* block = const_cast<block_t*>(find(pos));
return(reinterpret_cast<Type>(block->begin() + pos));
}
/**
Returns the size of the total stored data.
@return data size in bytes */
ulint size() const
MY_ATTRIBUTE((warn_unused_result))
{
#ifdef UNIV_DEBUG
ulint total_size = 0;
for (list_t::iterator it = m_list.begin(), end = m_list.end();
it != end; ++it) {
total_size += it->used();
}
ut_ad(total_size == m_size);
#endif /* UNIV_DEBUG */
return(m_size);
}
/**
Iterate over each block and call the functor.
@return false if iteration was terminated. */
template <typename Functor>
bool for_each_block(Functor& functor) const
{
for (list_t::iterator it = m_list.begin(), end = m_list.end();
it != end; ++it) {
if (!functor(&*it)) {
return false;
}
}
return(true);
}
/**
@return the first block */
block_t* front()
MY_ATTRIBUTE((warn_unused_result))
{
return &m_list.front();
}
/**
@return true if m_first_block block was not filled fully */
bool is_small() const
MY_ATTRIBUTE((warn_unused_result))
{
return(m_heap == NULL);
}
/** @return whether the buffer is empty */
bool empty() const { return !back()->m_used; }
private:
// Disable copying
mtr_buf_t(const mtr_buf_t&);
mtr_buf_t& operator=(const mtr_buf_t&);
/**
Add the block to the end of the list*/
void push_back(block_t* block)
{
block->init();
m_list.push_back(*block);
}
/** @return the last block in the list */
block_t* back() const
{
return &const_cast<block_t&>(m_list.back());
}
/*
@return true if request can be fullfilled */
bool has_space(ulint size) const
{
return(back()->m_used + size <= MAX_DATA_SIZE);
}
/*
@return true if request can be fullfilled */
bool has_space(ulint size)
{
return(back()->m_used + size <= MAX_DATA_SIZE);
}
/** Find the block that contains the pos.
@param pos absolute offset, it is updated to make it relative
to the block
@return the block containing the pos. */
block_t* find(ulint& pos)
{
ut_ad(!m_list.empty());
for (list_t::iterator it = m_list.begin(), end = m_list.end();
it != end; ++it) {
if (pos < it->used()) {
ut_ad(it->used() >= pos);
return &*it;
}
pos -= it->used();
}
return NULL;
}
/**
Allocate and add a new block to m_list */
block_t* add_block()
{
block_t* block;
if (m_heap == NULL) {
m_heap = mem_heap_create(sizeof(*block));
}
block = reinterpret_cast<block_t*>(
mem_heap_alloc(m_heap, sizeof(*block)));
push_back(block);
return(block);
}
private:
/** Heap to use for memory allocation */
mem_heap_t* m_heap;
/** Allocated blocks */
list_t m_list;
/** Total size used by all blocks */
ulint m_size;
/** The default block, should always be the first element. This
is for backwards compatibility and to avoid an extra heap allocation
for small REDO log records */
block_t m_first_block;
};
#endif /* dyn0buf_h */