/***************************************************************************** Copyright (c) 1995, 2017, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2017, 2020, 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 mtr/mtr0mtr.cc Mini-transaction buffer Created 11/26/1995 Heikki Tuuri *******************************************************/ #include "mtr0mtr.h" #include "buf0buf.h" #include "buf0flu.h" #include "fsp0sysspace.h" #include "page0types.h" #include "mtr0log.h" #include "log0recv.h" /** Iterate over a memo block in reverse. */ template struct CIterate { CIterate() : functor() {} CIterate(const Functor& functor) : functor(functor) {} /** @return false if the functor returns false. */ bool operator()(mtr_buf_t::block_t* block) const { const mtr_memo_slot_t* start = reinterpret_cast( block->begin()); mtr_memo_slot_t* slot = reinterpret_cast( block->end()); ut_ad(!(block->used() % sizeof(*slot))); while (slot-- != start) { if (!functor(slot)) { return(false); } } return(true); } Functor functor; }; template struct Iterate { Iterate() : functor() {} Iterate(const Functor& functor) : functor(functor) {} /** @return false if the functor returns false. */ bool operator()(mtr_buf_t::block_t* block) { const mtr_memo_slot_t* start = reinterpret_cast( block->begin()); mtr_memo_slot_t* slot = reinterpret_cast( block->end()); ut_ad(!(block->used() % sizeof(*slot))); while (slot-- != start) { if (!functor(slot)) { return(false); } } return(true); } Functor functor; }; /** Find specific object */ struct Find { /** Constructor */ Find(const void* object, ulint type) : m_slot(), m_type(type), m_object(object) { ut_a(object != NULL); } /** @return false if the object was found. */ bool operator()(mtr_memo_slot_t* slot) { if (m_object == slot->object && m_type == slot->type) { m_slot = slot; return(false); } return(true); } /** Slot if found */ mtr_memo_slot_t*m_slot; /** Type of the object to look for */ const ulint m_type; /** The object instance to look for */ const void* m_object; }; /** Find a page frame */ struct FindPage { /** Constructor @param[in] ptr pointer to within a page frame @param[in] flags MTR_MEMO flags to look for */ FindPage(const void* ptr, ulint flags) : m_ptr(ptr), m_flags(flags), m_slot(NULL) { /* There must be some flags to look for. */ ut_ad(flags); /* We can only look for page-related flags. */ ut_ad(!(flags & ulint(~(MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_BUF_FIX | MTR_MEMO_MODIFY)))); } /** Visit a memo entry. @param[in] slot memo entry to visit @retval false if a page was found @retval true if the iteration should continue */ bool operator()(mtr_memo_slot_t* slot) { ut_ad(m_slot == NULL); if (!(m_flags & slot->type) || slot->object == NULL) { return(true); } buf_block_t* block = reinterpret_cast( slot->object); if (m_ptr < block->frame || m_ptr >= block->frame + srv_page_size) { return(true); } ut_ad(!(m_flags & (MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_PAGE_X_FIX)) || rw_lock_own_flagged(&block->lock, m_flags)); m_slot = slot; return(false); } /** @return the slot that was found */ mtr_memo_slot_t* get_slot() const { ut_ad(m_slot != NULL); return(m_slot); } /** @return the block that was found */ buf_block_t* get_block() const { return(reinterpret_cast(get_slot()->object)); } private: /** Pointer inside a page frame to look for */ const void*const m_ptr; /** MTR_MEMO flags to look for */ const ulint m_flags; /** The slot corresponding to m_ptr */ mtr_memo_slot_t* m_slot; }; /** Release latches and decrement the buffer fix count. @param slot memo slot */ static void memo_slot_release(mtr_memo_slot_t *slot) { switch (slot->type) { case MTR_MEMO_S_LOCK: rw_lock_s_unlock(reinterpret_cast(slot->object)); break; case MTR_MEMO_SX_LOCK: rw_lock_sx_unlock(reinterpret_cast(slot->object)); break; case MTR_MEMO_SPACE_X_LOCK: { fil_space_t *space= static_cast(slot->object); space->set_committed_size(); rw_lock_x_unlock(&space->latch); } break; case MTR_MEMO_X_LOCK: rw_lock_x_unlock(reinterpret_cast(slot->object)); break; default: #ifdef UNIV_DEBUG switch (slot->type & ~MTR_MEMO_MODIFY) { case MTR_MEMO_BUF_FIX: case MTR_MEMO_PAGE_S_FIX: case MTR_MEMO_PAGE_SX_FIX: case MTR_MEMO_PAGE_X_FIX: break; default: ut_ad("invalid type" == 0); break; } #endif /* UNIV_DEBUG */ buf_block_t *block= reinterpret_cast(slot->object); buf_page_release_latch(block, slot->type & ~MTR_MEMO_MODIFY); block->unfix(); break; } slot->object= nullptr; } /** Release the latches acquired by the mini-transaction. */ struct ReleaseLatches { /** @return true always. */ bool operator()(mtr_memo_slot_t *slot) const { if (!slot->object) return true; switch (slot->type) { case MTR_MEMO_S_LOCK: rw_lock_s_unlock(reinterpret_cast(slot->object)); break; case MTR_MEMO_SPACE_X_LOCK: { fil_space_t *space= static_cast(slot->object); space->set_committed_size(); rw_lock_x_unlock(&space->latch); } break; case MTR_MEMO_X_LOCK: rw_lock_x_unlock(reinterpret_cast(slot->object)); break; case MTR_MEMO_SX_LOCK: rw_lock_sx_unlock(reinterpret_cast(slot->object)); break; default: #ifdef UNIV_DEBUG switch (slot->type & ~MTR_MEMO_MODIFY) { case MTR_MEMO_BUF_FIX: case MTR_MEMO_PAGE_S_FIX: case MTR_MEMO_PAGE_SX_FIX: case MTR_MEMO_PAGE_X_FIX: break; default: ut_ad("invalid type" == 0); break; } #endif /* UNIV_DEBUG */ buf_block_t *block= reinterpret_cast(slot->object); buf_page_release_latch(block, slot->type & ~MTR_MEMO_MODIFY); block->unfix(); break; } slot->object= NULL; return true; } }; /** Release the latches and blocks acquired by the mini-transaction. */ struct ReleaseAll { /** @return true always. */ bool operator()(mtr_memo_slot_t *slot) const { if (slot->object) memo_slot_release(slot); return true; } }; #ifdef UNIV_DEBUG /** Check that all slots have been handled. */ struct DebugCheck { /** @return true always. */ bool operator()(const mtr_memo_slot_t* slot) const { ut_ad(!slot->object); return(true); } }; #endif /** Release page latches held by the mini-transaction. */ struct ReleaseBlocks { const lsn_t start, end; #ifdef UNIV_DEBUG const mtr_buf_t &memo; ReleaseBlocks(lsn_t start, lsn_t end, const mtr_buf_t &memo) : start(start), end(end), memo(memo) #else /* UNIV_DEBUG */ ReleaseBlocks(lsn_t start, lsn_t end, const mtr_buf_t&) : start(start), end(end) #endif /* UNIV_DEBUG */ { ut_ad(start); ut_ad(end); } /** @return true always */ bool operator()(mtr_memo_slot_t* slot) const { if (!slot->object) return true; switch (slot->type) { case MTR_MEMO_PAGE_X_MODIFY: case MTR_MEMO_PAGE_SX_MODIFY: break; default: ut_ad(!(slot->type & MTR_MEMO_MODIFY)); return true; } buf_flush_note_modification(static_cast(slot->object), start, end); return true; } }; /** Start a mini-transaction. */ void mtr_t::start() { ut_ad(!m_freed_pages); ut_ad(!m_freed_space); MEM_UNDEFINED(this, sizeof *this); MEM_MAKE_DEFINED(&m_freed_space, sizeof m_freed_space); MEM_MAKE_DEFINED(&m_freed_pages, sizeof m_freed_pages); ut_d(m_start= true); ut_d(m_commit= false); m_last= nullptr; m_last_offset= 0; new(&m_memo) mtr_buf_t(); new(&m_log) mtr_buf_t(); m_made_dirty= false; m_inside_ibuf= false; m_modifications= false; m_log_mode= MTR_LOG_ALL; ut_d(m_user_space_id= TRX_SYS_SPACE); m_user_space= nullptr; m_commit_lsn= 0; m_trim_pages= false; } /** Release the resources */ inline void mtr_t::release_resources() { ut_ad(is_active()); ut_d(m_memo.for_each_block_in_reverse(CIterate())); m_log.erase(); m_memo.erase(); ut_d(m_commit= true); } /** Commit a mini-transaction. */ void mtr_t::commit() { ut_ad(is_active()); ut_ad(!is_inside_ibuf()); /* This is a dirty read, for debugging. */ ut_ad(!m_modifications || !recv_no_log_write); ut_ad(!m_modifications || m_log_mode != MTR_LOG_NONE); if (m_modifications && (m_log_mode == MTR_LOG_NO_REDO || !m_log.empty())) { ut_ad(!srv_read_only_mode || m_log_mode == MTR_LOG_NO_REDO); std::pair lsns; if (const ulint len= prepare_write()) lsns= finish_write(len); else lsns= { m_commit_lsn, PAGE_FLUSH_NO }; if (m_made_dirty) mysql_mutex_lock(&log_sys.flush_order_mutex); /* It is now safe to release the log mutex because the flush_order mutex will ensure that we are the first one to insert into the flush list. */ mysql_mutex_unlock(&log_sys.mutex); if (m_freed_pages) { ut_ad(!m_freed_pages->empty()); ut_ad(m_freed_space); ut_ad(memo_contains(*m_freed_space)); ut_ad(is_named_space(m_freed_space)); /* Update the last freed lsn */ m_freed_space->update_last_freed_lsn(m_commit_lsn); if (!is_trim_pages()) for (const auto &range : *m_freed_pages) m_freed_space->add_free_range(range); else m_freed_space->clear_freed_ranges(); delete m_freed_pages; m_freed_pages= nullptr; m_freed_space= nullptr; /* mtr_t::start() will reset m_trim_pages */ } else ut_ad(!m_freed_space); m_memo.for_each_block_in_reverse(CIterate (ReleaseBlocks(lsns.first, m_commit_lsn, m_memo))); if (m_made_dirty) mysql_mutex_unlock(&log_sys.flush_order_mutex); m_memo.for_each_block_in_reverse(CIterate()); if (UNIV_UNLIKELY(lsns.second != PAGE_FLUSH_NO)) buf_flush_ahead(m_commit_lsn, lsns.second == PAGE_FLUSH_SYNC); if (m_made_dirty) srv_stats.log_write_requests.inc(); } else m_memo.for_each_block_in_reverse(CIterate()); release_resources(); } /** Commit a mini-transaction that did not modify any pages, but generated some redo log on a higher level, such as FILE_MODIFY records and an optional FILE_CHECKPOINT marker. The caller must hold log_sys.mutex. This is to be used at log_checkpoint(). @param[in] checkpoint_lsn log checkpoint LSN, or 0 */ void mtr_t::commit_files(lsn_t checkpoint_lsn) { mysql_mutex_assert_owner(&log_sys.mutex); ut_ad(is_active()); ut_ad(!is_inside_ibuf()); ut_ad(m_log_mode == MTR_LOG_ALL); ut_ad(!m_made_dirty); ut_ad(m_memo.size() == 0); ut_ad(!srv_read_only_mode); ut_ad(!m_freed_space); ut_ad(!m_freed_pages); if (checkpoint_lsn) { byte* ptr = m_log.push(SIZE_OF_FILE_CHECKPOINT); compile_time_assert(SIZE_OF_FILE_CHECKPOINT == 3 + 8 + 1); *ptr = FILE_CHECKPOINT | (SIZE_OF_FILE_CHECKPOINT - 2); ::memset(ptr + 1, 0, 2); mach_write_to_8(ptr + 3, checkpoint_lsn); ptr[3 + 8] = 0; } else { *m_log.push(1) = 0; } finish_write(m_log.size()); srv_stats.log_write_requests.inc(); release_resources(); if (checkpoint_lsn) { DBUG_PRINT("ib_log", ("FILE_CHECKPOINT(" LSN_PF ") written at " LSN_PF, checkpoint_lsn, log_sys.get_lsn())); } } #ifdef UNIV_DEBUG /** Check if a tablespace is associated with the mini-transaction (needed for generating a FILE_MODIFY record) @param[in] space tablespace @return whether the mini-transaction is associated with the space */ bool mtr_t::is_named_space(ulint space) const { ut_ad(!m_user_space || m_user_space->id != TRX_SYS_SPACE); switch (m_log_mode) { case MTR_LOG_NONE: case MTR_LOG_NO_REDO: return(true); case MTR_LOG_ALL: return(m_user_space_id == space || is_predefined_tablespace(space)); } ut_error; return(false); } /** Check if a tablespace is associated with the mini-transaction (needed for generating a FILE_MODIFY record) @param[in] space tablespace @return whether the mini-transaction is associated with the space */ bool mtr_t::is_named_space(const fil_space_t* space) const { ut_ad(!m_user_space || m_user_space->id != TRX_SYS_SPACE); switch (m_log_mode) { case MTR_LOG_NONE: case MTR_LOG_NO_REDO: return true; case MTR_LOG_ALL: return m_user_space == space || is_predefined_tablespace(space->id); } ut_error; return false; } #endif /* UNIV_DEBUG */ /** Acquire a tablespace X-latch. NOTE: use mtr_x_lock_space(). @param[in] space_id tablespace ID @param[in] file file name from where called @param[in] line line number in file @return the tablespace object (never NULL) */ fil_space_t* mtr_t::x_lock_space(ulint space_id, const char* file, unsigned line) { fil_space_t* space; ut_ad(is_active()); if (space_id == TRX_SYS_SPACE) { space = fil_system.sys_space; } else if ((space = m_user_space) && space_id == space->id) { } else { space = fil_space_get(space_id); ut_ad(m_log_mode != MTR_LOG_NO_REDO || space->purpose == FIL_TYPE_TEMPORARY || space->purpose == FIL_TYPE_IMPORT); } ut_ad(space); ut_ad(space->id == space_id); x_lock_space(space, file, line); return(space); } /** Release an object in the memo stack. @return true if released */ bool mtr_t::memo_release(const void* object, ulint type) { ut_ad(is_active()); /* We cannot release a page that has been written to in the middle of a mini-transaction. */ ut_ad(!m_modifications || type != MTR_MEMO_PAGE_X_FIX); Iterate iteration(Find(object, type)); if (!m_memo.for_each_block_in_reverse(iteration)) { memo_slot_release(iteration.functor.m_slot); return(true); } return(false); } /** Release a page latch. @param[in] ptr pointer to within a page frame @param[in] type object type: MTR_MEMO_PAGE_X_FIX, ... */ void mtr_t::release_page(const void* ptr, mtr_memo_type_t type) { ut_ad(is_active()); /* We cannot release a page that has been written to in the middle of a mini-transaction. */ ut_ad(!m_modifications || type != MTR_MEMO_PAGE_X_FIX); Iterate iteration(FindPage(ptr, type)); if (!m_memo.for_each_block_in_reverse(iteration)) { memo_slot_release(iteration.functor.get_slot()); return; } /* The page was not found! */ ut_ad(0); } static bool log_margin_warned; static time_t log_margin_warn_time; static bool log_close_warned; static time_t log_close_warn_time; /** Check margin not to overwrite transaction log from the last checkpoint. If would estimate the log write to exceed the log_capacity, waits for the checkpoint is done enough. @param len length of the data to be written */ static void log_margin_checkpoint_age(ulint len) { const ulint framing_size= log_sys.framing_size(); /* actual length stored per block */ const ulint len_per_blk= OS_FILE_LOG_BLOCK_SIZE - framing_size; /* actual data length in last block already written */ ulint extra_len= log_sys.buf_free % OS_FILE_LOG_BLOCK_SIZE; ut_ad(extra_len >= LOG_BLOCK_HDR_SIZE); extra_len-= LOG_BLOCK_HDR_SIZE; /* total extra length for block header and trailer */ extra_len= ((len + extra_len) / len_per_blk) * framing_size; const ulint margin= len + extra_len; mysql_mutex_assert_owner(&log_sys.mutex); const lsn_t lsn= log_sys.get_lsn(); if (UNIV_UNLIKELY(margin > log_sys.log_capacity)) { time_t t= time(nullptr); /* return with warning output to avoid deadlock */ if (!log_margin_warned || difftime(t, log_margin_warn_time) > 15) { log_margin_warned= true; log_margin_warn_time= t; ib::error() << "innodb_log_file_size is too small " "for mini-transaction size " << len; } } else if (UNIV_LIKELY(lsn + margin <= log_sys.last_checkpoint_lsn + log_sys.log_capacity)) return; log_sys.set_check_flush_or_checkpoint(); } /** Open the log for log_write_low(). The log must be closed with log_close(). @param len length of the data to be written @return start lsn of the log record */ static lsn_t log_reserve_and_open(size_t len) { for (ut_d(ulint count= 0);;) { mysql_mutex_assert_owner(&log_sys.mutex); /* Calculate an upper limit for the space the string may take in the log buffer */ size_t len_upper_limit= (4 * OS_FILE_LOG_BLOCK_SIZE) + srv_log_write_ahead_size + (5 * len) / 4; if (log_sys.buf_free + len_upper_limit <= srv_log_buffer_size) break; mysql_mutex_unlock(&log_sys.mutex); DEBUG_SYNC_C("log_buf_size_exceeded"); /* Not enough free space, do a write of the log buffer */ log_write_up_to(log_sys.get_lsn(), false); srv_stats.log_waits.inc(); ut_ad(++count < 50); mysql_mutex_lock(&log_sys.mutex); } return log_sys.get_lsn(); } /** Append data to the log buffer. */ static void log_write_low(const void *str, size_t size) { mysql_mutex_assert_owner(&log_sys.mutex); const ulint trailer_offset= log_sys.trailer_offset(); do { /* Calculate a part length */ size_t len= size; size_t data_len= (log_sys.buf_free % OS_FILE_LOG_BLOCK_SIZE) + size; if (data_len > trailer_offset) { data_len= trailer_offset; len= trailer_offset - log_sys.buf_free % OS_FILE_LOG_BLOCK_SIZE; } memcpy(log_sys.buf + log_sys.buf_free, str, len); size-= len; str= static_cast(str) + len; byte *log_block= static_cast(ut_align_down(log_sys.buf + log_sys.buf_free, OS_FILE_LOG_BLOCK_SIZE)); log_block_set_data_len(log_block, data_len); lsn_t lsn= log_sys.get_lsn(); if (data_len == trailer_offset) { /* This block became full */ log_block_set_data_len(log_block, OS_FILE_LOG_BLOCK_SIZE); log_block_set_checkpoint_no(log_block, log_sys.next_checkpoint_no); len+= log_sys.framing_size(); lsn+= len; /* Initialize the next block header */ log_block_init(log_block + OS_FILE_LOG_BLOCK_SIZE, lsn); } else lsn+= len; log_sys.set_lsn(lsn); log_sys.buf_free+= len; ut_ad(log_sys.buf_free <= size_t{srv_log_buffer_size}); } while (size); } /** Close the log at mini-transaction commit. @return whether buffer pool flushing is needed */ static mtr_t::page_flush_ahead log_close(lsn_t lsn) { mysql_mutex_assert_owner(&log_sys.mutex); ut_ad(lsn == log_sys.get_lsn()); byte *log_block= static_cast(ut_align_down(log_sys.buf + log_sys.buf_free, OS_FILE_LOG_BLOCK_SIZE)); if (!log_block_get_first_rec_group(log_block)) { /* We initialized a new log block which was not written full by the current mtr: the next mtr log record group will start within this block at the offset data_len */ log_block_set_first_rec_group(log_block, log_block_get_data_len(log_block)); } if (log_sys.buf_free > log_sys.max_buf_free) log_sys.set_check_flush_or_checkpoint(); const lsn_t checkpoint_age= lsn - log_sys.last_checkpoint_lsn; if (UNIV_UNLIKELY(checkpoint_age >= log_sys.log_capacity) && /* silence message on create_log_file() after the log had been deleted */ checkpoint_age != lsn) { time_t t= time(nullptr); if (!log_close_warned || difftime(t, log_close_warn_time) > 15) { log_close_warned= true; log_close_warn_time= t; ib::error() << "The age of the last checkpoint is " << checkpoint_age << ", which exceeds the log capacity " << log_sys.log_capacity << "."; } } else if (UNIV_LIKELY(checkpoint_age <= log_sys.max_modified_age_async)) return mtr_t::PAGE_FLUSH_NO; else if (UNIV_LIKELY(checkpoint_age <= log_sys.max_checkpoint_age)) return mtr_t::PAGE_FLUSH_ASYNC; log_sys.set_check_flush_or_checkpoint(); return mtr_t::PAGE_FLUSH_SYNC; } /** Write the block contents to the REDO log */ struct mtr_write_log { /** Append a block to the redo log buffer. @return whether the appending should continue */ bool operator()(const mtr_buf_t::block_t *block) const { log_write_low(block->begin(), block->used()); return true; } }; /** Prepare to write the mini-transaction log to the redo log buffer. @return number of bytes to write in finish_write() */ inline ulint mtr_t::prepare_write() { ut_ad(!recv_no_log_write); if (UNIV_UNLIKELY(m_log_mode != MTR_LOG_ALL)) { ut_ad(m_log_mode == MTR_LOG_NO_REDO); ut_ad(m_log.size() == 0); mysql_mutex_lock(&log_sys.mutex); m_commit_lsn = log_sys.get_lsn(); return 0; } ulint len = m_log.size(); ut_ad(len > 0); if (len > srv_log_buffer_size / 2) { log_buffer_extend(ulong((len + 1) * 2)); } fil_space_t* space = m_user_space; if (space != NULL && is_predefined_tablespace(space->id)) { /* Omit FILE_MODIFY for predefined tablespaces. */ space = NULL; } mysql_mutex_lock(&log_sys.mutex); if (fil_names_write_if_was_clean(space)) { len = m_log.size(); } else { /* This was not the first time of dirtying a tablespace since the latest checkpoint. */ ut_ad(len == m_log.size()); } *m_log.push(1) = 0; len++; /* check and attempt a checkpoint if exceeding capacity */ log_margin_checkpoint_age(len); return(len); } /** Append the redo log records to the redo log buffer. @param len number of bytes to write @return {start_lsn,flush_ahead} */ inline std::pair mtr_t::finish_write(ulint len) { ut_ad(m_log_mode == MTR_LOG_ALL); mysql_mutex_assert_owner(&log_sys.mutex); ut_ad(m_log.size() == len); ut_ad(len > 0); lsn_t start_lsn; if (m_log.is_small()) { const mtr_buf_t::block_t* front = m_log.front(); ut_ad(len <= front->used()); m_commit_lsn = log_reserve_and_write_fast(front->begin(), len, &start_lsn); if (!m_commit_lsn) { goto piecewise; } } else { piecewise: /* Open the database log for log_write_low */ start_lsn = log_reserve_and_open(len); mtr_write_log write_log; m_log.for_each_block(write_log); m_commit_lsn = log_sys.get_lsn(); } page_flush_ahead flush= log_close(m_commit_lsn); DBUG_EXECUTE_IF("ib_log_flush_ahead", flush = PAGE_FLUSH_SYNC;); return std::make_pair(start_lsn, flush); } /** Find out whether a block was not X-latched by the mini-transaction */ struct FindBlockX { const buf_block_t █ FindBlockX(const buf_block_t &block): block(block) {} /** @return whether the block was not found x-latched */ bool operator()(const mtr_memo_slot_t *slot) const { return slot->object != &block || slot->type != MTR_MEMO_PAGE_X_FIX; } }; #ifdef UNIV_DEBUG /** Assert that the block is not present in the mini-transaction */ struct FindNoBlock { const buf_block_t █ FindNoBlock(const buf_block_t &block): block(block) {} /** @return whether the block was not found */ bool operator()(const mtr_memo_slot_t *slot) const { return slot->object != █ } }; #endif /* UNIV_DEBUG */ bool mtr_t::have_x_latch(const buf_block_t &block) const { if (m_memo.for_each_block(CIterate(FindBlockX(block)))) { ut_ad(m_memo.for_each_block(CIterate(FindNoBlock(block)))); ut_ad(!memo_contains_flagged(&block, MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_BUF_FIX | MTR_MEMO_MODIFY)); return false; } ut_ad(rw_lock_own(&block.lock, RW_LOCK_X)); return true; } #ifdef UNIV_DEBUG /** Check if we are holding an rw-latch in this mini-transaction @param lock latch to search for @param type held latch type @return whether (lock,type) is contained */ bool mtr_t::memo_contains(const rw_lock_t &lock, mtr_memo_type_t type) { Iterate iteration(Find(&lock, type)); if (m_memo.for_each_block_in_reverse(iteration)) return false; switch (type) { case MTR_MEMO_X_LOCK: ut_ad(rw_lock_own(&lock, RW_LOCK_X)); break; case MTR_MEMO_SX_LOCK: ut_ad(rw_lock_own(&lock, RW_LOCK_SX)); break; case MTR_MEMO_S_LOCK: ut_ad(rw_lock_own(&lock, RW_LOCK_S)); break; default: break; } return true; } /** Check if we are holding exclusive tablespace latch @param space tablespace to search for @return whether space.latch is being held */ bool mtr_t::memo_contains(const fil_space_t& space) { Iterate iteration(Find(&space, MTR_MEMO_SPACE_X_LOCK)); if (m_memo.for_each_block_in_reverse(iteration)) return false; ut_ad(rw_lock_own(const_cast(&space.latch), RW_LOCK_X)); return true; } /** Debug check for flags */ struct FlaggedCheck { FlaggedCheck(const void* ptr, ulint flags) : m_ptr(ptr), m_flags(flags) { /* There must be some flags to look for. */ ut_ad(flags); /* Look for rw-lock-related and page-related flags. */ ut_ad(!(flags & ulint(~(MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_BUF_FIX | MTR_MEMO_MODIFY | MTR_MEMO_X_LOCK | MTR_MEMO_SX_LOCK | MTR_MEMO_S_LOCK)))); /* Either some rw-lock-related or page-related flags must be specified, but not both at the same time. */ ut_ad(!(flags & (MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_BUF_FIX | MTR_MEMO_MODIFY)) == !!(flags & (MTR_MEMO_X_LOCK | MTR_MEMO_SX_LOCK | MTR_MEMO_S_LOCK))); } /** Visit a memo entry. @param[in] slot memo entry to visit @retval false if m_ptr was found @retval true if the iteration should continue */ bool operator()(const mtr_memo_slot_t* slot) const { if (m_ptr != slot->object || !(m_flags & slot->type)) { return(true); } if (ulint flags = m_flags & (MTR_MEMO_PAGE_S_FIX | MTR_MEMO_PAGE_SX_FIX | MTR_MEMO_PAGE_X_FIX)) { rw_lock_t* lock = &static_cast( const_cast(m_ptr))->lock; ut_ad(rw_lock_own_flagged(lock, flags)); } else { rw_lock_t* lock = static_cast( const_cast(m_ptr)); ut_ad(rw_lock_own_flagged(lock, m_flags >> 5)); } return(false); } const void*const m_ptr; const ulint m_flags; }; /** Check if memo contains the given item. @param object object to search @param flags specify types of object (can be ORred) of MTR_MEMO_PAGE_S_FIX ... values @return true if contains */ bool mtr_t::memo_contains_flagged(const void* ptr, ulint flags) const { ut_ad(is_active()); return !m_memo.for_each_block_in_reverse( CIterate(FlaggedCheck(ptr, flags))); } /** Check if memo contains the given page. @param[in] ptr pointer to within buffer frame @param[in] flags specify types of object with OR of MTR_MEMO_PAGE_S_FIX... values @return the block @retval NULL if not found */ buf_block_t* mtr_t::memo_contains_page_flagged( const byte* ptr, ulint flags) const { Iterate iteration(FindPage(ptr, flags)); return m_memo.for_each_block_in_reverse(iteration) ? NULL : iteration.functor.get_block(); } /** Print info of an mtr handle. */ void mtr_t::print() const { ib::info() << "Mini-transaction handle: memo size " << m_memo.size() << " bytes log size " << get_log()->size() << " bytes"; } #endif /* UNIV_DEBUG */ /** Find a block, preferrably in MTR_MEMO_MODIFY state */ struct FindModified { mtr_memo_slot_t *found= nullptr; const buf_block_t& block; FindModified(const buf_block_t &block) : block(block) {} bool operator()(mtr_memo_slot_t *slot) { if (slot->object != &block) return true; found= slot; return !(slot->type & (MTR_MEMO_MODIFY | MTR_MEMO_PAGE_X_FIX | MTR_MEMO_PAGE_SX_FIX)); } }; /** Mark the given latched page as modified. @param block page that will be modified */ void mtr_t::modify(const buf_block_t &block) { if (UNIV_UNLIKELY(m_memo.empty())) { /* This must be PageConverter::update_page() in IMPORT TABLESPACE. */ ut_ad(!block.page.in_LRU_list); ut_ad(!buf_pool.is_uncompressed(&block)); return; } Iterate iteration((FindModified(block))); if (UNIV_UNLIKELY(m_memo.for_each_block(iteration))) { ut_ad("modifying an unlatched page" == 0); return; } iteration.functor.found->type= static_cast (iteration.functor.found->type | MTR_MEMO_MODIFY); }