mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 04:46:15 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			1513 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1513 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*****************************************************************************
 | 
						|
 | 
						|
Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved.
 | 
						|
Copyright (c) 2014, 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 trx/trx0undo.cc
 | 
						|
Transaction undo log
 | 
						|
 | 
						|
Created 3/26/1996 Heikki Tuuri
 | 
						|
*******************************************************/
 | 
						|
 | 
						|
#include "trx0undo.h"
 | 
						|
#include "buf0rea.h"
 | 
						|
#include "fsp0fsp.h"
 | 
						|
#include "mtr0log.h"
 | 
						|
#include "srv0mon.h"
 | 
						|
#include "srv0srv.h"
 | 
						|
#include "srv0start.h"
 | 
						|
#include "trx0purge.h"
 | 
						|
#include "trx0rec.h"
 | 
						|
#include "trx0rseg.h"
 | 
						|
#include "log.h"
 | 
						|
 | 
						|
/* How should the old versions in the history list be managed?
 | 
						|
   ----------------------------------------------------------
 | 
						|
If each transaction is given a whole page for its update undo log, file
 | 
						|
space consumption can be 10 times higher than necessary. Therefore,
 | 
						|
partly filled update undo log pages should be reusable. But then there
 | 
						|
is no way individual pages can be ordered so that the ordering agrees
 | 
						|
with the serialization numbers of the transactions on the pages. Thus,
 | 
						|
the history list must be formed of undo logs, not their header pages as
 | 
						|
it was in the old implementation.
 | 
						|
	However, on a single header page the transactions are placed in
 | 
						|
the order of their serialization numbers. As old versions are purged, we
 | 
						|
may free the page when the last transaction on the page has been purged.
 | 
						|
	A problem is that the purge has to go through the transactions
 | 
						|
in the serialization order. This means that we have to look through all
 | 
						|
rollback segments for the one that has the smallest transaction number
 | 
						|
in its history list.
 | 
						|
	When should we do a purge? A purge is necessary when space is
 | 
						|
running out in any of the rollback segments. Then we may have to purge
 | 
						|
also old version which might be needed by some consistent read. How do
 | 
						|
we trigger the start of a purge? When a transaction writes to an undo log,
 | 
						|
it may notice that the space is running out. When a read view is closed,
 | 
						|
it may make some history superfluous. The server can have an utility which
 | 
						|
periodically checks if it can purge some history.
 | 
						|
	In a parallelized purge we have the problem that a query thread
 | 
						|
can remove a delete marked clustered index record before another query
 | 
						|
thread has processed an earlier version of the record, which cannot then
 | 
						|
be done because the row cannot be constructed from the clustered index
 | 
						|
record. To avoid this problem, we will store in the update and delete mark
 | 
						|
undo record also the columns necessary to construct the secondary index
 | 
						|
entries which are modified.
 | 
						|
	We can latch the stack of versions of a single clustered index record
 | 
						|
by taking a latch on the clustered index page. As long as the latch is held,
 | 
						|
no new versions can be added and no versions removed by undo. But, a purge
 | 
						|
can still remove old versions from the bottom of the stack. */
 | 
						|
 | 
						|
/* How to protect rollback segments, undo logs, and history lists with
 | 
						|
   -------------------------------------------------------------------
 | 
						|
latches?
 | 
						|
-------
 | 
						|
When a transaction does its first insert or modify in the clustered index, an
 | 
						|
undo log is assigned for it. Then we must have an x-latch to the rollback
 | 
						|
segment header.
 | 
						|
	When the transaction performs modifications or rolls back, its
 | 
						|
undo log is protected by undo page latches.
 | 
						|
Only the thread that is associated with the transaction may hold multiple
 | 
						|
undo page latches at a time. Undo pages are always private to a single
 | 
						|
transaction. Other threads that are performing MVCC reads
 | 
						|
or checking for implicit locks will lock at most one undo page at a time
 | 
						|
in trx_undo_get_undo_rec_low().
 | 
						|
	When the transaction commits, its persistent undo log is added
 | 
						|
to the history list. If it is not suitable for reuse, its slot is reset.
 | 
						|
In both cases, an x-latch must be acquired on the rollback segment header page.
 | 
						|
	The purge operation steps through the history list without modifying
 | 
						|
it until a truncate operation occurs, which can remove undo logs from the end
 | 
						|
of the list and release undo log segments. In stepping through the list,
 | 
						|
s-latches on the undo log pages are enough, but in a truncate, x-latches must
 | 
						|
be obtained on the rollback segment and individual pages. */
 | 
						|
 | 
						|
/********************************************************************//**
 | 
						|
Creates and initializes an undo log memory object.
 | 
						|
@return own: the undo log memory object */
 | 
						|
static
 | 
						|
trx_undo_t*
 | 
						|
trx_undo_mem_create(
 | 
						|
/*================*/
 | 
						|
	trx_rseg_t*	rseg,	/*!< in: rollback segment memory object */
 | 
						|
	ulint		id,	/*!< in: slot index within rseg */
 | 
						|
	trx_id_t	trx_id,	/*!< in: id of the trx for which the undo log
 | 
						|
				is created */
 | 
						|
	const XID*	xid,	/*!< in: X/Open XA transaction identification*/
 | 
						|
	uint32_t	page_no,/*!< in: undo log header page number */
 | 
						|
	uint16_t	offset);/*!< in: undo log header byte offset on page */
 | 
						|
 | 
						|
/** Determine the start offset of undo log records of an undo log page.
 | 
						|
@param[in]	block	undo log page
 | 
						|
@param[in]	page_no		undo log header page number
 | 
						|
@param[in]	offset		undo log header offset
 | 
						|
@return start offset */
 | 
						|
static
 | 
						|
uint16_t trx_undo_page_get_start(const buf_block_t *block, uint32_t page_no,
 | 
						|
                                 uint16_t offset)
 | 
						|
{
 | 
						|
  return page_no == block->page.id().page_no()
 | 
						|
    ? mach_read_from_2(offset + TRX_UNDO_LOG_START + block->page.frame)
 | 
						|
    : TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE;
 | 
						|
}
 | 
						|
 | 
						|
/** Get the first undo log record on a page.
 | 
						|
@param[in]	block	undo log page
 | 
						|
@param[in]	page_no	undo log header page number
 | 
						|
@param[in]	offset	undo log header page offset
 | 
						|
@return	pointer to first record
 | 
						|
@retval	nullptr	if none exists */
 | 
						|
trx_undo_rec_t*
 | 
						|
trx_undo_page_get_first_rec(const buf_block_t *block, uint32_t page_no,
 | 
						|
                            uint16_t offset)
 | 
						|
{
 | 
						|
  uint16_t start= trx_undo_page_get_start(block, page_no, offset);
 | 
						|
  uint16_t end= trx_undo_page_get_end(block, page_no, offset);
 | 
						|
  ut_ad(start <= end);
 | 
						|
  return start >= end ? nullptr : block->page.frame + start;
 | 
						|
}
 | 
						|
 | 
						|
/** Get the last undo log record on a page.
 | 
						|
@param[in]	page	undo log page
 | 
						|
@param[in]	page_no	undo log header page number
 | 
						|
@param[in]	offset	undo log header page offset
 | 
						|
@return	pointer to last record
 | 
						|
@retval	NULL	if none exists */
 | 
						|
static
 | 
						|
trx_undo_rec_t*
 | 
						|
trx_undo_page_get_last_rec(const buf_block_t *block, uint32_t page_no,
 | 
						|
                           uint16_t offset)
 | 
						|
{
 | 
						|
  uint16_t start= trx_undo_page_get_start(block, page_no, offset);
 | 
						|
  uint16_t end= trx_undo_page_get_end(block, page_no, offset);
 | 
						|
  ut_ad(start <= end);
 | 
						|
  return start >= end
 | 
						|
    ? nullptr
 | 
						|
    : block->page.frame + mach_read_from_2(block->page.frame + end - 2);
 | 
						|
}
 | 
						|
 | 
						|
/** Get the previous record in an undo log from the previous page.
 | 
						|
@param[in,out]  block   undo log page
 | 
						|
@param[in]      rec     undo record offset in the page
 | 
						|
@param[in]      page_no undo log header page number
 | 
						|
@param[in]      offset  undo log header offset on page
 | 
						|
@param[in]      shared  latching mode: true=RW_S_LATCH, false=RW_X_LATCH
 | 
						|
@param[in,out]  mtr     mini-transaction
 | 
						|
@return undo log record, the page latched, NULL if none */
 | 
						|
static trx_undo_rec_t*
 | 
						|
trx_undo_get_prev_rec_from_prev_page(buf_block_t *&block, uint16_t rec,
 | 
						|
                                     uint32_t page_no, uint16_t offset,
 | 
						|
                                     bool shared, mtr_t *mtr)
 | 
						|
{
 | 
						|
  uint32_t prev_page_no= mach_read_from_4(TRX_UNDO_PAGE_HDR +
 | 
						|
                                          TRX_UNDO_PAGE_NODE +
 | 
						|
                                          FLST_PREV + FIL_ADDR_PAGE +
 | 
						|
                                          block->page.frame);
 | 
						|
 | 
						|
  if (prev_page_no == FIL_NULL)
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  block= buf_page_get(page_id_t(block->page.id().space(), prev_page_no),
 | 
						|
                      0, shared ? RW_S_LATCH : RW_X_LATCH, mtr);
 | 
						|
  if (UNIV_UNLIKELY(!block))
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  if (!buf_page_make_young_if_needed(&block->page))
 | 
						|
    buf_read_ahead_linear(block->page.id());
 | 
						|
  return trx_undo_page_get_last_rec(block, page_no, offset);
 | 
						|
}
 | 
						|
 | 
						|
/** Get the previous undo log record.
 | 
						|
@param[in]	block	undo log page
 | 
						|
@param[in]	rec	undo log record
 | 
						|
@param[in]	page_no	undo log header page number
 | 
						|
@param[in]	offset	undo log header page offset
 | 
						|
@return	pointer to record
 | 
						|
@retval	NULL if none */
 | 
						|
static
 | 
						|
trx_undo_rec_t*
 | 
						|
trx_undo_page_get_prev_rec(const buf_block_t *block, trx_undo_rec_t *rec,
 | 
						|
                           uint32_t page_no, uint16_t offset)
 | 
						|
{
 | 
						|
  ut_ad(block->page.frame == page_align(rec));
 | 
						|
  return
 | 
						|
    rec == block->page.frame + trx_undo_page_get_start(block, page_no, offset)
 | 
						|
    ? nullptr
 | 
						|
    : block->page.frame + mach_read_from_2(rec - 2);
 | 
						|
}
 | 
						|
 | 
						|
/** Get the previous record in an undo log.
 | 
						|
@param[in,out]  block   undo log page
 | 
						|
@param[in]      rec     undo record offset in the page
 | 
						|
@param[in]      page_no undo log header page number
 | 
						|
@param[in]      offset  undo log header offset on page
 | 
						|
@param[in]      shared  latching mode: true=RW_S_LATCH, false=RW_X_LATCH
 | 
						|
@param[in,out]  mtr     mini-transaction
 | 
						|
@return undo log record, the page latched, NULL if none */
 | 
						|
trx_undo_rec_t*
 | 
						|
trx_undo_get_prev_rec(buf_block_t *&block, uint16_t rec, uint32_t page_no,
 | 
						|
                      uint16_t offset, bool shared, mtr_t *mtr)
 | 
						|
{
 | 
						|
  if (trx_undo_rec_t *prev= trx_undo_page_get_prev_rec(block,
 | 
						|
                                                       block->page.frame + rec,
 | 
						|
                                                       page_no, offset))
 | 
						|
    return prev;
 | 
						|
 | 
						|
  /* We have to go to the previous undo log page to look for the
 | 
						|
  previous record */
 | 
						|
 | 
						|
  return trx_undo_get_prev_rec_from_prev_page(block, rec, page_no, offset,
 | 
						|
                                              shared, mtr);
 | 
						|
}
 | 
						|
 | 
						|
/** Get the next record in an undo log from the next page.
 | 
						|
@param[in,out]  block   undo log page
 | 
						|
@param[in]      page_no undo log header page number
 | 
						|
@param[in]      offset  undo log header offset on page
 | 
						|
@param[in]      mode    latching mode: RW_S_LATCH or RW_X_LATCH
 | 
						|
@param[in,out]  mtr     mini-transaction
 | 
						|
@return undo log record, the page latched, NULL if none */
 | 
						|
static trx_undo_rec_t*
 | 
						|
trx_undo_get_next_rec_from_next_page(const buf_block_t *&block,
 | 
						|
                                     uint32_t page_no, uint16_t offset,
 | 
						|
                                     rw_lock_type_t mode, mtr_t *mtr)
 | 
						|
{
 | 
						|
  if (page_no == block->page.id().page_no() &&
 | 
						|
      mach_read_from_2(block->page.frame + offset + TRX_UNDO_NEXT_LOG))
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  uint32_t next= mach_read_from_4(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE +
 | 
						|
                                  FLST_NEXT + FIL_ADDR_PAGE +
 | 
						|
                                  block->page.frame);
 | 
						|
  if (next == FIL_NULL)
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  block= buf_page_get_gen(page_id_t(block->page.id().space(), next), 0, mode,
 | 
						|
                          nullptr, BUF_GET_POSSIBLY_FREED, mtr);
 | 
						|
 | 
						|
  return block ? trx_undo_page_get_first_rec(block, page_no, offset) : nullptr;
 | 
						|
}
 | 
						|
 | 
						|
/** Get the first record in an undo log.
 | 
						|
@param[in]      space   undo log header space
 | 
						|
@param[in]      page_no undo log header page number
 | 
						|
@param[in]      offset  undo log header offset on page
 | 
						|
@param[in]      mode    latching mode: RW_S_LATCH or RW_X_LATCH
 | 
						|
@param[out]     block   undo log page
 | 
						|
@param[in,out]  mtr     mini-transaction
 | 
						|
@param[out]     err     error code
 | 
						|
@return undo log record, the page latched
 | 
						|
@retval nullptr if none */
 | 
						|
static trx_undo_rec_t*
 | 
						|
trx_undo_get_first_rec(const fil_space_t &space, uint32_t page_no,
 | 
						|
                       uint16_t offset, rw_lock_type_t mode,
 | 
						|
                       const buf_block_t *&block,
 | 
						|
                       mtr_t *mtr, dberr_t *err)
 | 
						|
{
 | 
						|
  buf_block_t *b= buf_page_get_gen(page_id_t{space.id, page_no}, 0, mode,
 | 
						|
                                   nullptr, BUF_GET, mtr, err);
 | 
						|
  block= b;
 | 
						|
  if (!block)
 | 
						|
    return nullptr;
 | 
						|
 | 
						|
  if (!buf_page_make_young_if_needed(&b->page))
 | 
						|
    buf_read_ahead_linear(b->page.id());
 | 
						|
 | 
						|
  if (trx_undo_rec_t *rec= trx_undo_page_get_first_rec(b, page_no, offset))
 | 
						|
    return rec;
 | 
						|
 | 
						|
  return trx_undo_get_next_rec_from_next_page(block, page_no, offset, mode,
 | 
						|
                                              mtr);
 | 
						|
}
 | 
						|
 | 
						|
inline
 | 
						|
void UndorecApplier::apply_undo_rec(const trx_undo_rec_t *rec, uint16_t offset)
 | 
						|
{
 | 
						|
  undo_rec= rec;
 | 
						|
  ut_ad(page_offset(undo_rec) == offset);
 | 
						|
  this->offset= offset;
 | 
						|
 | 
						|
  bool updated_extern= false;
 | 
						|
  undo_no_t undo_no= 0;
 | 
						|
  table_id_t table_id= 0;
 | 
						|
  undo_rec= trx_undo_rec_get_pars(undo_rec, &type,
 | 
						|
                                  &cmpl_info,
 | 
						|
                                  &updated_extern, &undo_no, &table_id);
 | 
						|
  dict_sys.freeze(SRW_LOCK_CALL);
 | 
						|
  dict_table_t *table= dict_sys.find_table(table_id);
 | 
						|
  dict_sys.unfreeze();
 | 
						|
 | 
						|
  ut_ad(table);
 | 
						|
  if (!table->is_native_online_ddl())
 | 
						|
    return;
 | 
						|
 | 
						|
  dict_index_t *index= dict_table_get_first_index(table);
 | 
						|
  const dtuple_t *undo_tuple;
 | 
						|
  switch (type) {
 | 
						|
  default:
 | 
						|
    ut_ad("invalid type" == 0);
 | 
						|
    MY_ASSERT_UNREACHABLE();
 | 
						|
  case TRX_UNDO_INSERT_REC:
 | 
						|
    undo_rec= trx_undo_rec_get_row_ref(undo_rec, index, &undo_tuple, heap);
 | 
						|
  insert:
 | 
						|
    log_insert(*undo_tuple, index);
 | 
						|
    break;
 | 
						|
  case TRX_UNDO_UPD_EXIST_REC:
 | 
						|
  case TRX_UNDO_UPD_DEL_REC:
 | 
						|
  case TRX_UNDO_DEL_MARK_REC:
 | 
						|
    trx_id_t trx_id;
 | 
						|
    roll_ptr_t roll_ptr;
 | 
						|
    byte info_bits;
 | 
						|
    undo_rec= trx_undo_update_rec_get_sys_cols(
 | 
						|
      undo_rec, &trx_id, &roll_ptr, &info_bits);
 | 
						|
 | 
						|
    undo_rec= trx_undo_rec_get_row_ref(undo_rec, index, &undo_tuple, heap);
 | 
						|
    undo_rec= trx_undo_update_rec_get_update(undo_rec, index, type, trx_id,
 | 
						|
                                             roll_ptr, info_bits,
 | 
						|
                                             heap, &update);
 | 
						|
    if (type == TRX_UNDO_UPD_DEL_REC)
 | 
						|
      goto insert;
 | 
						|
    log_update(*undo_tuple, index);
 | 
						|
  }
 | 
						|
 | 
						|
  clear_undo_rec();
 | 
						|
}
 | 
						|
 | 
						|
/** Apply any changes to tables for which online DDL is in progress. */
 | 
						|
ATTRIBUTE_COLD void trx_t::apply_log()
 | 
						|
{
 | 
						|
  const trx_undo_t *undo= rsegs.m_redo.undo;
 | 
						|
  if (!undo || !undo_no)
 | 
						|
    return;
 | 
						|
  const page_id_t page_id{rsegs.m_redo.rseg->space->id, undo->hdr_page_no};
 | 
						|
  page_id_t next_page_id(page_id);
 | 
						|
  buf_block_t *block= buf_pool.page_fix(page_id, nullptr, buf_pool_t::FIX_WAIT_READ);
 | 
						|
  if (UNIV_UNLIKELY(!block))
 | 
						|
    return;
 | 
						|
 | 
						|
  UndorecApplier log_applier(page_id, id);
 | 
						|
 | 
						|
  for (;;)
 | 
						|
  {
 | 
						|
    trx_undo_rec_t *rec= trx_undo_page_get_first_rec(block, page_id.page_no(),
 | 
						|
                                                     undo->hdr_offset);
 | 
						|
    while (rec)
 | 
						|
    {
 | 
						|
      const uint16_t offset= uint16_t(rec - block->page.frame);
 | 
						|
      /* Since we are the only thread who could write to this undo page,
 | 
						|
      it is safe to dereference rec while only holding a buffer-fix. */
 | 
						|
      log_applier.apply_undo_rec(rec, offset);
 | 
						|
      rec= trx_undo_page_get_next_rec(block, offset,
 | 
						|
                                      page_id.page_no(), undo->hdr_offset);
 | 
						|
    }
 | 
						|
 | 
						|
    uint32_t next= mach_read_from_4(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE +
 | 
						|
                                    FLST_NEXT + FIL_ADDR_PAGE +
 | 
						|
                                    block->page.frame);
 | 
						|
    block->page.unfix();
 | 
						|
    if (next == FIL_NULL)
 | 
						|
      break;
 | 
						|
    next_page_id.set_page_no(next);
 | 
						|
    block= buf_pool.page_fix(next_page_id, nullptr, buf_pool_t::FIX_WAIT_READ);
 | 
						|
    if (UNIV_UNLIKELY(!block))
 | 
						|
      break;
 | 
						|
    log_applier.assign_next(next_page_id);
 | 
						|
  }
 | 
						|
 | 
						|
  apply_online_log= false;
 | 
						|
}
 | 
						|
 | 
						|
/*============== UNDO LOG FILE COPY CREATION AND FREEING ==================*/
 | 
						|
 | 
						|
/** Initialize an undo log page.
 | 
						|
NOTE: This corresponds to a redo log record and must not be changed!
 | 
						|
@see mtr_t::undo_create()
 | 
						|
@param block   undo log page */
 | 
						|
void trx_undo_page_init(const buf_block_t &block)
 | 
						|
{
 | 
						|
  mach_write_to_2(my_assume_aligned<2>(FIL_PAGE_TYPE + block.page.frame),
 | 
						|
                  FIL_PAGE_UNDO_LOG);
 | 
						|
  static_assert(TRX_UNDO_PAGE_HDR == FIL_PAGE_DATA, "compatibility");
 | 
						|
  memset_aligned<2>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE + block.page.frame,
 | 
						|
                    0, 2);
 | 
						|
  mach_write_to_2(my_assume_aligned<2>
 | 
						|
                  (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_START + block.page.frame),
 | 
						|
                  TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_HDR_SIZE);
 | 
						|
  memcpy_aligned<2>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE + block.page.frame,
 | 
						|
                    TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_START + block.page.frame,
 | 
						|
                    2);
 | 
						|
  /* The following corresponds to flst_zero_both(), but without writing log. */
 | 
						|
  memset_aligned<4>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE + FLST_PREV +
 | 
						|
                    FIL_ADDR_PAGE + block.page.frame, 0xff, 4);
 | 
						|
  memset_aligned<2>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE + FLST_PREV +
 | 
						|
                    FIL_ADDR_BYTE + block.page.frame, 0, 2);
 | 
						|
  memset_aligned<2>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE + FLST_NEXT +
 | 
						|
                    FIL_ADDR_PAGE + block.page.frame, 0xff, 4);
 | 
						|
  memset_aligned<2>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE + FLST_NEXT +
 | 
						|
                    FIL_ADDR_BYTE + block.page.frame, 0, 2);
 | 
						|
  static_assert(TRX_UNDO_PAGE_NODE + FLST_NEXT + FIL_ADDR_BYTE + 2 ==
 | 
						|
                TRX_UNDO_PAGE_HDR_SIZE, "compatibility");
 | 
						|
  /* Preserve TRX_UNDO_SEG_HDR, but clear the rest of the page. */
 | 
						|
  memset_aligned<2>(TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE +
 | 
						|
                    block.page.frame, 0,
 | 
						|
                    srv_page_size - (TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE +
 | 
						|
                                     FIL_PAGE_DATA_END));
 | 
						|
}
 | 
						|
 | 
						|
/** Look for a free slot for an undo log segment.
 | 
						|
@param rseg_header   rollback segment header
 | 
						|
@return slot index
 | 
						|
@retval ULINT_UNDEFINED if not found */
 | 
						|
static ulint trx_rsegf_undo_find_free(const buf_block_t *rseg_header)
 | 
						|
{
 | 
						|
  ulint max_slots= TRX_RSEG_N_SLOTS;
 | 
						|
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
  if (trx_rseg_n_slots_debug)
 | 
						|
    max_slots= std::min<ulint>(trx_rseg_n_slots_debug, TRX_RSEG_N_SLOTS);
 | 
						|
#endif
 | 
						|
 | 
						|
  for (ulint i= 0; i < max_slots; i++)
 | 
						|
    if (trx_rsegf_get_nth_undo(rseg_header, i) == FIL_NULL)
 | 
						|
      return i;
 | 
						|
 | 
						|
  return ULINT_UNDEFINED;
 | 
						|
}
 | 
						|
 | 
						|
/** Create an undo log segment.
 | 
						|
@param[in,out]	space		tablespace
 | 
						|
@param[in,out]	rseg_hdr	rollback segment header (x-latched)
 | 
						|
@param[out]	id		undo slot number
 | 
						|
@param[out]	err		error code
 | 
						|
@param[in,out]	mtr		mini-transaction
 | 
						|
@return	undo log block
 | 
						|
@retval	NULL	on failure */
 | 
						|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
buf_block_t*
 | 
						|
trx_undo_seg_create(fil_space_t *space, buf_block_t *rseg_hdr, ulint *id,
 | 
						|
                    dberr_t *err, mtr_t *mtr)
 | 
						|
{
 | 
						|
	buf_block_t*	block;
 | 
						|
	uint32_t	n_reserved;
 | 
						|
 | 
						|
	const ulint slot_no = trx_rsegf_undo_find_free(rseg_hdr);
 | 
						|
 | 
						|
	if (slot_no == ULINT_UNDEFINED) {
 | 
						|
		ib::warn() << "Cannot find a free slot for an undo log. Do"
 | 
						|
			" you have too many active transactions running"
 | 
						|
			" concurrently?";
 | 
						|
 | 
						|
		*err = DB_TOO_MANY_CONCURRENT_TRXS;
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	ut_ad(slot_no < TRX_RSEG_N_SLOTS);
 | 
						|
 | 
						|
	*err = fsp_reserve_free_extents(&n_reserved, space, 2, FSP_UNDO, mtr);
 | 
						|
	if (UNIV_UNLIKELY(*err != DB_SUCCESS)) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Allocate a new file segment for the undo log */
 | 
						|
	block = fseg_create(space, TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER,
 | 
						|
			    mtr, err, true);
 | 
						|
 | 
						|
	space->release_free_extents(n_reserved);
 | 
						|
 | 
						|
	if (!block) {
 | 
						|
		return block;
 | 
						|
	}
 | 
						|
 | 
						|
	mtr->undo_create(*block);
 | 
						|
	trx_undo_page_init(*block);
 | 
						|
 | 
						|
	mtr->write<2>(*block, TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE
 | 
						|
		      + block->page.frame,
 | 
						|
		      TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE);
 | 
						|
	mtr->write<2,mtr_t::MAYBE_NOP>(*block,
 | 
						|
				       TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG
 | 
						|
				       + block->page.frame, 0U);
 | 
						|
 | 
						|
	flst_init(*block, TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST
 | 
						|
		  + block->page.frame, mtr);
 | 
						|
 | 
						|
	*err = flst_add_last(block, TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST,
 | 
						|
			     block, TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE,
 | 
						|
			     space->free_limit, mtr);
 | 
						|
 | 
						|
	*id = slot_no;
 | 
						|
	mtr->write<4>(*rseg_hdr, TRX_RSEG + TRX_RSEG_UNDO_SLOTS
 | 
						|
		      + slot_no * TRX_RSEG_SLOT_SIZE + rseg_hdr->page.frame,
 | 
						|
		      block->page.id().page_no());
 | 
						|
 | 
						|
	*err = DB_SUCCESS;
 | 
						|
	return block;
 | 
						|
}
 | 
						|
 | 
						|
/** Initialize an undo log header.
 | 
						|
@param[in,out]  undo_page   undo log segment header page
 | 
						|
@param[in]      trx_id      transaction identifier
 | 
						|
@param[in,out]  mtr         mini-transaction
 | 
						|
@return header byte offset on page */
 | 
						|
static uint16_t trx_undo_header_create(buf_block_t *undo_page, trx_id_t trx_id,
 | 
						|
                                       mtr_t* mtr)
 | 
						|
{
 | 
						|
  /* Reset the TRX_UNDO_PAGE_TYPE in case this page is being
 | 
						|
  repurposed after upgrading to MariaDB 10.3. */
 | 
						|
  byte *undo_type= my_assume_aligned<2>
 | 
						|
    (TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_TYPE + undo_page->page.frame);
 | 
						|
  ut_ad(mach_read_from_2(undo_type) <= 2);
 | 
						|
  mtr->write<2,mtr_t::MAYBE_NOP>(*undo_page, undo_type, 0U);
 | 
						|
  byte *start= my_assume_aligned<4>(TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_START +
 | 
						|
                                    undo_page->page.frame);
 | 
						|
  const uint16_t free= mach_read_from_2(start + 2);
 | 
						|
  static_assert(TRX_UNDO_PAGE_START + 2 == TRX_UNDO_PAGE_FREE,
 | 
						|
                "compatibility");
 | 
						|
  ut_a(free + TRX_UNDO_LOG_XA_HDR_SIZE < srv_page_size - 100);
 | 
						|
 | 
						|
  mach_write_to_2(start, free + TRX_UNDO_LOG_XA_HDR_SIZE);
 | 
						|
  /* A WRITE of 2 bytes is never longer than a MEMMOVE.
 | 
						|
  So, WRITE 2+2 bytes is better than WRITE+MEMMOVE.
 | 
						|
  But, a MEMSET will only be 1+2 bytes, that is, 1 byte shorter! */
 | 
						|
  memcpy_aligned<2>(start + 2, start, 2);
 | 
						|
  mtr->memset(*undo_page, TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_START, 4,
 | 
						|
              start, 2);
 | 
						|
  uint16_t prev_log= mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG +
 | 
						|
                                      undo_page->page.frame);
 | 
						|
  ut_ad(prev_log < free);
 | 
						|
  alignas(4) byte buf[4];
 | 
						|
  mach_write_to_2(buf, TRX_UNDO_ACTIVE);
 | 
						|
  mach_write_to_2(buf + 2, free);
 | 
						|
  static_assert(TRX_UNDO_STATE + 2 == TRX_UNDO_LAST_LOG, "compatibility");
 | 
						|
  static_assert(!((TRX_UNDO_SEG_HDR + TRX_UNDO_STATE) % 4), "alignment");
 | 
						|
  mtr->memcpy<mtr_t::MAYBE_NOP>
 | 
						|
    (*undo_page, my_assume_aligned<4>
 | 
						|
     (TRX_UNDO_SEG_HDR + TRX_UNDO_STATE + undo_page->page.frame), buf, 4);
 | 
						|
  if (prev_log)
 | 
						|
    mtr->write<2>(*undo_page, prev_log + TRX_UNDO_NEXT_LOG +
 | 
						|
                  undo_page->page.frame, free);
 | 
						|
  mtr->write<8,mtr_t::MAYBE_NOP>(*undo_page, free + TRX_UNDO_TRX_ID +
 | 
						|
                                 undo_page->page.frame, trx_id);
 | 
						|
  if (UNIV_UNLIKELY(mach_read_from_8(free + TRX_UNDO_TRX_NO +
 | 
						|
                                     undo_page->page.frame) != 0))
 | 
						|
    mtr->memset(undo_page, free + TRX_UNDO_TRX_NO, 8, 0);
 | 
						|
 | 
						|
  mtr->memcpy<mtr_t::MAYBE_NOP>(*undo_page, free + TRX_UNDO_LOG_START +
 | 
						|
                                undo_page->page.frame, start, 2);
 | 
						|
  /* Initialize all fields TRX_UNDO_XID_EXISTS to TRX_UNDO_HISTORY_NODE. */
 | 
						|
  if (prev_log)
 | 
						|
  {
 | 
						|
    mtr->memset(undo_page, free + TRX_UNDO_XID_EXISTS,
 | 
						|
                TRX_UNDO_PREV_LOG - TRX_UNDO_XID_EXISTS, 0);
 | 
						|
    mtr->write<2,mtr_t::MAYBE_NOP>(*undo_page, free + TRX_UNDO_PREV_LOG +
 | 
						|
                                   undo_page->page.frame, prev_log);
 | 
						|
    static_assert(TRX_UNDO_PREV_LOG + 2 == TRX_UNDO_HISTORY_NODE,
 | 
						|
                  "compatibility");
 | 
						|
    mtr->memset(undo_page, free + TRX_UNDO_HISTORY_NODE, FLST_NODE_SIZE, 0);
 | 
						|
    static_assert(TRX_UNDO_LOG_OLD_HDR_SIZE == TRX_UNDO_HISTORY_NODE +
 | 
						|
                  FLST_NODE_SIZE, "compatibility");
 | 
						|
  }
 | 
						|
  else
 | 
						|
    mtr->memset(undo_page, free + TRX_UNDO_XID_EXISTS,
 | 
						|
                TRX_UNDO_LOG_OLD_HDR_SIZE - TRX_UNDO_XID_EXISTS, 0);
 | 
						|
  return free;
 | 
						|
}
 | 
						|
 | 
						|
/** Write X/Open XA Transaction Identifier (XID) to undo log header
 | 
						|
@param[in,out]  block   undo header page
 | 
						|
@param[in]      offset  undo header record offset
 | 
						|
@param[in]      xid     distributed transaction identifier
 | 
						|
@param[in,out]  mtr     mini-transaction */
 | 
						|
static void trx_undo_write_xid(buf_block_t *block, uint16_t offset,
 | 
						|
                               const XID &xid, mtr_t *mtr)
 | 
						|
{
 | 
						|
  DBUG_ASSERT(xid.gtrid_length > 0);
 | 
						|
  DBUG_ASSERT(xid.bqual_length >= 0);
 | 
						|
  DBUG_ASSERT(xid.gtrid_length <= MAXGTRIDSIZE);
 | 
						|
  DBUG_ASSERT(xid.bqual_length <= MAXBQUALSIZE);
 | 
						|
  static_assert(MAXGTRIDSIZE + MAXBQUALSIZE == XIDDATASIZE,
 | 
						|
                "gtrid and bqual don't fit xid data");
 | 
						|
  DBUG_ASSERT(mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG +
 | 
						|
                               block->page.frame) == offset);
 | 
						|
 | 
						|
  trx_ulogf_t* log_hdr= block->page.frame + offset;
 | 
						|
 | 
						|
  mtr->write<4,mtr_t::MAYBE_NOP>(*block, log_hdr + TRX_UNDO_XA_FORMAT,
 | 
						|
                                 static_cast<uint32_t>(xid.formatID));
 | 
						|
  mtr->write<4,mtr_t::MAYBE_NOP>(*block, log_hdr + TRX_UNDO_XA_TRID_LEN,
 | 
						|
                                 static_cast<uint32_t>(xid.gtrid_length));
 | 
						|
  mtr->write<4,mtr_t::MAYBE_NOP>(*block, log_hdr + TRX_UNDO_XA_BQUAL_LEN,
 | 
						|
                                 static_cast<uint32_t>(xid.bqual_length));
 | 
						|
  const ulint xid_length= static_cast<ulint>(xid.gtrid_length
 | 
						|
                                             + xid.bqual_length);
 | 
						|
  mtr->memcpy<mtr_t::MAYBE_NOP>(*block,
 | 
						|
                                &block->page.frame[offset + TRX_UNDO_XA_XID],
 | 
						|
                                xid.data, xid_length);
 | 
						|
  if (UNIV_LIKELY(xid_length < XIDDATASIZE))
 | 
						|
    mtr->memset(block, offset + TRX_UNDO_XA_XID + xid_length,
 | 
						|
                XIDDATASIZE - xid_length, 0);
 | 
						|
}
 | 
						|
 | 
						|
/********************************************************************//**
 | 
						|
Read X/Open XA Transaction Identification (XID) from undo log header */
 | 
						|
static
 | 
						|
void
 | 
						|
trx_undo_read_xid(const trx_ulogf_t* log_hdr, XID* xid)
 | 
						|
{
 | 
						|
	xid->formatID=static_cast<long>(mach_read_from_4(
 | 
						|
		log_hdr + TRX_UNDO_XA_FORMAT));
 | 
						|
 | 
						|
	xid->gtrid_length=static_cast<long>(mach_read_from_4(
 | 
						|
		log_hdr + TRX_UNDO_XA_TRID_LEN));
 | 
						|
 | 
						|
	xid->bqual_length=static_cast<long>(mach_read_from_4(
 | 
						|
		log_hdr + TRX_UNDO_XA_BQUAL_LEN));
 | 
						|
 | 
						|
	memcpy(xid->data, log_hdr + TRX_UNDO_XA_XID, XIDDATASIZE);
 | 
						|
}
 | 
						|
 | 
						|
/** Allocate an undo log page.
 | 
						|
@param[in,out]	undo	undo log
 | 
						|
@param[in,out]	mtr	mini-transaction that does not hold any page latch
 | 
						|
@param[out]	err	error code
 | 
						|
@return	X-latched block if success
 | 
						|
@retval	nullptr	on failure */
 | 
						|
buf_block_t *trx_undo_add_page(trx_undo_t *undo, mtr_t *mtr, dberr_t *err)
 | 
						|
{
 | 
						|
  buf_block_t *new_block= nullptr;
 | 
						|
  uint32_t n_reserved;
 | 
						|
 | 
						|
  /* When we add a page to an undo log, this is analogous to
 | 
						|
   a pessimistic insert in a B-tree, and we must reserve the
 | 
						|
   counterpart of the tree latch, which is the rseg mutex. */
 | 
						|
 | 
						|
  trx_rseg_t *rseg= undo->rseg;
 | 
						|
  rseg->latch.wr_lock(SRW_LOCK_CALL);
 | 
						|
 | 
						|
  buf_block_t *header_block=
 | 
						|
    buf_page_get_gen(page_id_t{rseg->space->id, undo->hdr_page_no},
 | 
						|
                     0, RW_X_LATCH, nullptr, BUF_GET, mtr, err);
 | 
						|
  if (!header_block)
 | 
						|
    goto func_exit;
 | 
						|
  buf_page_make_young_if_needed(&header_block->page);
 | 
						|
 | 
						|
  *err= fsp_reserve_free_extents(&n_reserved, rseg->space, 1, FSP_UNDO, mtr);
 | 
						|
 | 
						|
  if (UNIV_UNLIKELY(*err != DB_SUCCESS))
 | 
						|
    goto func_exit;
 | 
						|
 | 
						|
  new_block=
 | 
						|
    fseg_alloc_free_page_general(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER +
 | 
						|
                                 header_block->page.frame,
 | 
						|
                                 undo->top_page_no + 1, FSP_UP, true,
 | 
						|
                                 mtr, mtr, err);
 | 
						|
  rseg->space->release_free_extents(n_reserved);
 | 
						|
 | 
						|
  if (!new_block)
 | 
						|
    goto func_exit;
 | 
						|
 | 
						|
  undo->last_page_no= new_block->page.id().page_no();
 | 
						|
 | 
						|
  mtr->undo_create(*new_block);
 | 
						|
  trx_undo_page_init(*new_block);
 | 
						|
  *err= flst_add_last(header_block, TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST,
 | 
						|
                      new_block, TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE,
 | 
						|
                      rseg->space->free_limit, mtr);
 | 
						|
  if (UNIV_UNLIKELY(*err != DB_SUCCESS))
 | 
						|
    new_block= nullptr;
 | 
						|
  else
 | 
						|
  {
 | 
						|
    undo->size++;
 | 
						|
    rseg->curr_size++;
 | 
						|
  }
 | 
						|
 | 
						|
func_exit:
 | 
						|
  rseg->latch.wr_unlock();
 | 
						|
  return new_block;
 | 
						|
}
 | 
						|
 | 
						|
/********************************************************************//**
 | 
						|
Frees an undo log page that is not the header page.
 | 
						|
@return last page number in remaining log */
 | 
						|
static
 | 
						|
uint32_t
 | 
						|
trx_undo_free_page(
 | 
						|
/*===============*/
 | 
						|
	trx_rseg_t* rseg,	/*!< in: rollback segment */
 | 
						|
	bool	in_history,	/*!< in: TRUE if the undo log is in the history
 | 
						|
				list */
 | 
						|
	uint32_t hdr_page_no,	/*!< in: header page number */
 | 
						|
	uint32_t page_no,	/*!< in: page number to free: must not be the
 | 
						|
				header page */
 | 
						|
	mtr_t*	mtr,		/*!< in: mtr which does not have a latch to any
 | 
						|
				undo log page; the caller must have reserved
 | 
						|
				the rollback segment mutex */
 | 
						|
	dberr_t* err)		/*!< out: error code */
 | 
						|
{
 | 
						|
	ut_a(hdr_page_no != page_no);
 | 
						|
 | 
						|
	buf_block_t* undo_block = buf_page_get_gen(page_id_t(rseg->space->id,
 | 
						|
							     page_no),
 | 
						|
						   0, RW_X_LATCH, nullptr,
 | 
						|
						   BUF_GET, mtr, err);
 | 
						|
	if (UNIV_UNLIKELY(!undo_block)) {
 | 
						|
		return FIL_NULL;
 | 
						|
	}
 | 
						|
	buf_block_t* header_block = buf_page_get_gen(page_id_t(rseg->space->id,
 | 
						|
							       hdr_page_no),
 | 
						|
						     0, RW_X_LATCH, nullptr,
 | 
						|
						     BUF_GET, mtr, err);
 | 
						|
	if (UNIV_UNLIKELY(!header_block)) {
 | 
						|
		return FIL_NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	buf_page_make_young_if_needed(&header_block->page);
 | 
						|
 | 
						|
	const uint32_t limit = rseg->space->free_limit;
 | 
						|
 | 
						|
	*err = flst_remove(header_block, TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST,
 | 
						|
			   undo_block, TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE,
 | 
						|
			   limit, mtr);
 | 
						|
 | 
						|
	if (UNIV_UNLIKELY(*err != DB_SUCCESS)) {
 | 
						|
		return FIL_NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	const fil_addr_t last_addr = flst_get_last(
 | 
						|
		TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST
 | 
						|
		+ header_block->page.frame);
 | 
						|
	if (UNIV_UNLIKELY(last_addr.page == page_no)
 | 
						|
	    || UNIV_UNLIKELY(last_addr.page != FIL_NULL
 | 
						|
			     && last_addr.page >= limit)
 | 
						|
	    || UNIV_UNLIKELY(last_addr.boffset < TRX_UNDO_PAGE_HDR
 | 
						|
			     + TRX_UNDO_PAGE_NODE)
 | 
						|
	    || UNIV_UNLIKELY(last_addr.boffset >= srv_page_size
 | 
						|
			     - TRX_UNDO_LOG_OLD_HDR_SIZE)) {
 | 
						|
		*err = DB_CORRUPTION;
 | 
						|
		return FIL_NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	*err = fseg_free_page(TRX_UNDO_SEG_HDR + TRX_UNDO_FSEG_HEADER
 | 
						|
			      + header_block->page.frame,
 | 
						|
			      rseg->space, page_no, mtr);
 | 
						|
	if (UNIV_UNLIKELY(*err != DB_SUCCESS)) {
 | 
						|
		return FIL_NULL;
 | 
						|
	}
 | 
						|
	buf_page_free(rseg->space, page_no, mtr);
 | 
						|
 | 
						|
	rseg->curr_size--;
 | 
						|
 | 
						|
	if (!in_history) {
 | 
						|
	} else if (buf_block_t* rseg_header = rseg->get(mtr, err)) {
 | 
						|
		byte* rseg_hist_size = TRX_RSEG + TRX_RSEG_HISTORY_SIZE
 | 
						|
			+ rseg_header->page.frame;
 | 
						|
		uint32_t hist_size = mach_read_from_4(rseg_hist_size);
 | 
						|
		ut_ad(hist_size > 0);
 | 
						|
		mtr->write<4>(*rseg_header, rseg_hist_size, hist_size - 1);
 | 
						|
	} else {
 | 
						|
		return FIL_NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	return(last_addr.page);
 | 
						|
}
 | 
						|
 | 
						|
/** Free the last undo log page. The caller must hold the rseg mutex.
 | 
						|
@param[in,out]	undo	undo log
 | 
						|
@param[in,out]	mtr	mini-transaction that does not hold any undo log page
 | 
						|
			or that has allocated the undo log page
 | 
						|
@return error code */
 | 
						|
dberr_t trx_undo_free_last_page(trx_undo_t *undo, mtr_t *mtr)
 | 
						|
{
 | 
						|
  ut_ad(undo->hdr_page_no != undo->last_page_no);
 | 
						|
  ut_ad(undo->size > 0);
 | 
						|
  undo->size--;
 | 
						|
 | 
						|
  dberr_t err;
 | 
						|
  undo->last_page_no= trx_undo_free_page(undo->rseg, false, undo->hdr_page_no,
 | 
						|
                                         undo->last_page_no, mtr, &err);
 | 
						|
  return err;
 | 
						|
}
 | 
						|
 | 
						|
/** Truncate the tail of an undo log during rollback.
 | 
						|
@param[in,out]	undo	undo log
 | 
						|
@param[in]	limit	all undo logs after this limit will be discarded
 | 
						|
@param[in]	is_temp	whether this is temporary undo log
 | 
						|
@return error code */
 | 
						|
static dberr_t trx_undo_truncate_end(trx_undo_t &undo, undo_no_t limit,
 | 
						|
                                     bool is_temp)
 | 
						|
{
 | 
						|
  ut_ad(is_temp == !undo.rseg->is_persistent());
 | 
						|
 | 
						|
  if (UNIV_UNLIKELY(undo.last_page_no == FIL_NULL))
 | 
						|
    return DB_CORRUPTION;
 | 
						|
 | 
						|
  for (mtr_t mtr;;)
 | 
						|
  {
 | 
						|
    mtr.start();
 | 
						|
    if (is_temp)
 | 
						|
      mtr.set_log_mode(MTR_LOG_NO_REDO);
 | 
						|
 | 
						|
    trx_undo_rec_t *trunc_here= nullptr;
 | 
						|
    undo.rseg->latch.wr_lock(SRW_LOCK_CALL);
 | 
						|
    dberr_t err;
 | 
						|
    buf_block_t *undo_block=
 | 
						|
      buf_page_get_gen(page_id_t{undo.rseg->space->id, undo.last_page_no},
 | 
						|
                       0, RW_X_LATCH, nullptr, BUF_GET, &mtr, &err);
 | 
						|
    if (UNIV_UNLIKELY(!undo_block))
 | 
						|
      goto func_exit;
 | 
						|
 | 
						|
    for (trx_undo_rec_t *rec=
 | 
						|
           trx_undo_page_get_last_rec(undo_block,
 | 
						|
                                      undo.hdr_page_no, undo.hdr_offset);
 | 
						|
         rec; )
 | 
						|
    {
 | 
						|
      if (trx_undo_rec_get_undo_no(rec) < limit)
 | 
						|
        goto func_exit;
 | 
						|
      /* Truncate at least this record off, maybe more */
 | 
						|
      trunc_here= rec;
 | 
						|
      rec= trx_undo_page_get_prev_rec(undo_block, rec,
 | 
						|
                                      undo.hdr_page_no, undo.hdr_offset);
 | 
						|
    }
 | 
						|
 | 
						|
    if (undo.last_page_no != undo.hdr_page_no)
 | 
						|
    {
 | 
						|
      err= trx_undo_free_last_page(&undo, &mtr);
 | 
						|
      if (UNIV_UNLIKELY(err != DB_SUCCESS))
 | 
						|
        goto func_exit;
 | 
						|
      undo.rseg->latch.wr_unlock();
 | 
						|
      mtr.commit();
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
func_exit:
 | 
						|
    undo.rseg->latch.wr_unlock();
 | 
						|
 | 
						|
    if (trunc_here && err == DB_SUCCESS)
 | 
						|
      mtr.write<2>(*undo_block, TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_FREE +
 | 
						|
                   undo_block->page.frame,
 | 
						|
                   ulint(trunc_here - undo_block->page.frame));
 | 
						|
 | 
						|
    mtr.commit();
 | 
						|
    return err;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/** Try to truncate the undo logs.
 | 
						|
@param trx transaction
 | 
						|
@return error code */
 | 
						|
dberr_t trx_undo_try_truncate(const trx_t &trx)
 | 
						|
{
 | 
						|
  if (trx_undo_t *undo= trx.rsegs.m_redo.undo)
 | 
						|
  {
 | 
						|
    ut_ad(undo->rseg == trx.rsegs.m_redo.rseg);
 | 
						|
    if (dberr_t err= trx_undo_truncate_end(*undo, trx.undo_no, false))
 | 
						|
      return err;
 | 
						|
  }
 | 
						|
 | 
						|
  if (trx_undo_t *undo = trx.rsegs.m_noredo.undo)
 | 
						|
  {
 | 
						|
    ut_ad(undo->rseg == trx.rsegs.m_noredo.rseg);
 | 
						|
    if (dberr_t err= trx_undo_truncate_end(*undo, trx.undo_no, true))
 | 
						|
      return err;
 | 
						|
  }
 | 
						|
 | 
						|
  return DB_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
/** Truncate the head of an undo log.
 | 
						|
NOTE that only whole pages are freed; the header page is not
 | 
						|
freed, but emptied, if all the records there are below the limit.
 | 
						|
@param[in,out]	rseg		rollback segment
 | 
						|
@param[in]	hdr_page_no	header page number
 | 
						|
@param[in]	hdr_offset	header offset on the page
 | 
						|
@param[in]	limit		first undo number to preserve
 | 
						|
(everything below the limit will be truncated)
 | 
						|
@return error code  */
 | 
						|
dberr_t
 | 
						|
trx_undo_truncate_start(
 | 
						|
	trx_rseg_t*	rseg,
 | 
						|
	uint32_t	hdr_page_no,
 | 
						|
	uint16_t	hdr_offset,
 | 
						|
	undo_no_t	limit)
 | 
						|
{
 | 
						|
	trx_undo_rec_t* rec;
 | 
						|
	trx_undo_rec_t* last_rec;
 | 
						|
	mtr_t		mtr;
 | 
						|
 | 
						|
	ut_ad(rseg->is_persistent());
 | 
						|
 | 
						|
	if (!limit) {
 | 
						|
		return DB_SUCCESS;
 | 
						|
	}
 | 
						|
loop:
 | 
						|
	mtr.start();
 | 
						|
 | 
						|
	dberr_t err;
 | 
						|
	const buf_block_t* undo_page;
 | 
						|
	rec = trx_undo_get_first_rec(*rseg->space, hdr_page_no, hdr_offset,
 | 
						|
				     RW_X_LATCH, undo_page, &mtr, &err);
 | 
						|
	if (rec == NULL) {
 | 
						|
		/* Already empty */
 | 
						|
done:
 | 
						|
		mtr.commit();
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	last_rec = trx_undo_page_get_last_rec(undo_page, hdr_page_no,
 | 
						|
					      hdr_offset);
 | 
						|
	if (trx_undo_rec_get_undo_no(last_rec) >= limit) {
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	if (undo_page->page.id().page_no() == hdr_page_no) {
 | 
						|
		uint16_t end = mach_read_from_2(hdr_offset + TRX_UNDO_NEXT_LOG
 | 
						|
						+ undo_page->page.frame);
 | 
						|
		if (end == 0) {
 | 
						|
			end = mach_read_from_2(TRX_UNDO_PAGE_HDR
 | 
						|
					       + TRX_UNDO_PAGE_FREE
 | 
						|
					       + undo_page->page.frame);
 | 
						|
		}
 | 
						|
 | 
						|
		mtr.write<2>(*undo_page, undo_page->page.frame + hdr_offset
 | 
						|
			     + TRX_UNDO_LOG_START, end);
 | 
						|
	} else {
 | 
						|
		trx_undo_free_page(rseg, true, hdr_page_no,
 | 
						|
				   undo_page->page.id().page_no(), &mtr, &err);
 | 
						|
		if (err != DB_SUCCESS) {
 | 
						|
			goto done;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	mtr.commit();
 | 
						|
	goto loop;
 | 
						|
}
 | 
						|
 | 
						|
/*========== UNDO LOG MEMORY COPY INITIALIZATION =====================*/
 | 
						|
 | 
						|
/** Read an undo log when starting up the database.
 | 
						|
@param[in,out]	rseg		rollback segment
 | 
						|
@param[in]	id		rollback segment slot
 | 
						|
@param[in]	page_no		undo log segment page number
 | 
						|
@return	the undo log
 | 
						|
@retval nullptr on error */
 | 
						|
trx_undo_t *
 | 
						|
trx_undo_mem_create_at_db_start(trx_rseg_t *rseg, ulint id, uint32_t page_no)
 | 
						|
{
 | 
						|
	mtr_t		mtr;
 | 
						|
	XID		xid;
 | 
						|
 | 
						|
	ut_ad(id < TRX_RSEG_N_SLOTS);
 | 
						|
 | 
						|
	mtr.start();
 | 
						|
	const page_id_t page_id{rseg->space->id, page_no};
 | 
						|
	const buf_block_t* block = recv_sys.recover(page_id, &mtr, nullptr);
 | 
						|
	if (UNIV_UNLIKELY(!block)) {
 | 
						|
corrupted:
 | 
						|
		mtr.commit();
 | 
						|
		return nullptr;
 | 
						|
	}
 | 
						|
 | 
						|
	const uint16_t type = mach_read_from_2(TRX_UNDO_PAGE_HDR
 | 
						|
					       + TRX_UNDO_PAGE_TYPE
 | 
						|
					       + block->page.frame);
 | 
						|
	if (UNIV_UNLIKELY(type > 2)) {
 | 
						|
corrupted_type:
 | 
						|
		sql_print_error("InnoDB: unsupported undo header type %u",
 | 
						|
				type);
 | 
						|
		goto corrupted;
 | 
						|
	}
 | 
						|
 | 
						|
	uint16_t offset = mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG
 | 
						|
					   + block->page.frame);
 | 
						|
	if (offset < TRX_UNDO_SEG_HDR + TRX_UNDO_SEG_HDR_SIZE ||
 | 
						|
	    offset >= srv_page_size - TRX_UNDO_LOG_OLD_HDR_SIZE) {
 | 
						|
		sql_print_error("InnoDB: invalid undo header offset %u",
 | 
						|
				offset);
 | 
						|
		goto corrupted;
 | 
						|
	}
 | 
						|
 | 
						|
	const trx_ulogf_t* const undo_header = block->page.frame + offset;
 | 
						|
	uint16_t state = mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
 | 
						|
					  + block->page.frame);
 | 
						|
 | 
						|
	const trx_id_t trx_id= mach_read_from_8(undo_header + TRX_UNDO_TRX_ID);
 | 
						|
	if (trx_id >> 48) {
 | 
						|
		sql_print_error("InnoDB: corrupted TRX_ID %" PRIx64, trx_id);
 | 
						|
		goto corrupted;
 | 
						|
	}
 | 
						|
	/* We will increment rseg->needs_purge, like trx_undo_reuse_cached()
 | 
						|
	would do it, to avoid trouble on rollback or XA COMMIT. */
 | 
						|
	trx_id_t trx_no = trx_id + 1;
 | 
						|
 | 
						|
	switch (state) {
 | 
						|
	case TRX_UNDO_ACTIVE:
 | 
						|
	case TRX_UNDO_PREPARED:
 | 
						|
		if (UNIV_LIKELY(type != 1)) {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		sql_print_error("InnoDB: upgrade from older version than"
 | 
						|
				" MariaDB 10.3 requires clean shutdown");
 | 
						|
		goto corrupted;
 | 
						|
	default:
 | 
						|
		sql_print_error("InnoDB: unsupported undo header state %u",
 | 
						|
				state);
 | 
						|
		goto corrupted;
 | 
						|
	case TRX_UNDO_CACHED:
 | 
						|
		if (UNIV_UNLIKELY(type != 0)) {
 | 
						|
			/* This undo page was not updated by MariaDB
 | 
						|
			10.3 or later. The TRX_UNDO_TRX_NO field may
 | 
						|
			contain garbage. */
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		goto read_trx_no;
 | 
						|
	case TRX_UNDO_TO_PURGE:
 | 
						|
		if (UNIV_UNLIKELY(type == 1)) {
 | 
						|
			goto corrupted_type;
 | 
						|
		}
 | 
						|
	read_trx_no:
 | 
						|
		trx_no = mach_read_from_8(TRX_UNDO_TRX_NO + undo_header);
 | 
						|
		if (trx_no >> 48) {
 | 
						|
			sql_print_error("InnoDB: corrupted TRX_NO %" PRIx64,
 | 
						|
					trx_no);
 | 
						|
			goto corrupted;
 | 
						|
		}
 | 
						|
		if (trx_no < trx_id) {
 | 
						|
			trx_no = trx_id;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Read X/Open XA transaction identification if it exists, or
 | 
						|
	set it to NULL. */
 | 
						|
 | 
						|
	if (undo_header[TRX_UNDO_XID_EXISTS]) {
 | 
						|
		trx_undo_read_xid(undo_header, &xid);
 | 
						|
	} else {
 | 
						|
		xid.null();
 | 
						|
	}
 | 
						|
 | 
						|
	if (trx_no > rseg->needs_purge) {
 | 
						|
		rseg->needs_purge = trx_no;
 | 
						|
	}
 | 
						|
 | 
						|
	trx_undo_t* undo = trx_undo_mem_create(
 | 
						|
		rseg, id, trx_id, &xid, page_no, offset);
 | 
						|
	if (!undo) {
 | 
						|
		return undo;
 | 
						|
	}
 | 
						|
 | 
						|
	undo->dict_operation = undo_header[TRX_UNDO_DICT_TRANS];
 | 
						|
	undo->size = flst_get_len(TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST
 | 
						|
				  + block->page.frame);
 | 
						|
 | 
						|
	fil_addr_t	last_addr = flst_get_last(
 | 
						|
		TRX_UNDO_SEG_HDR + TRX_UNDO_PAGE_LIST + block->page.frame);
 | 
						|
 | 
						|
	if (last_addr.page >= rseg->space->free_limit
 | 
						|
	    || last_addr.boffset < TRX_UNDO_PAGE_HDR + TRX_UNDO_PAGE_NODE
 | 
						|
	    || last_addr.boffset >= srv_page_size
 | 
						|
	    - TRX_UNDO_LOG_OLD_HDR_SIZE) {
 | 
						|
	corrupted_undo:
 | 
						|
		ut_free(undo);
 | 
						|
		goto corrupted;
 | 
						|
	}
 | 
						|
 | 
						|
	undo->last_page_no = last_addr.page;
 | 
						|
	undo->top_page_no = last_addr.page;
 | 
						|
 | 
						|
	const buf_block_t* last = recv_sys.recover(
 | 
						|
		page_id_t(rseg->space->id, undo->last_page_no), &mtr, nullptr);
 | 
						|
 | 
						|
	if (UNIV_UNLIKELY(!last)) {
 | 
						|
		goto corrupted_undo;
 | 
						|
        }
 | 
						|
 | 
						|
	if (const trx_undo_rec_t* rec = trx_undo_page_get_last_rec(
 | 
						|
		    last, page_no, offset)) {
 | 
						|
		undo->top_offset = static_cast<uint16_t>(
 | 
						|
			rec - last->page.frame);
 | 
						|
		undo->top_undo_no = trx_undo_rec_get_undo_no(rec);
 | 
						|
		ut_ad(!undo->empty());
 | 
						|
	} else {
 | 
						|
		undo->top_undo_no = IB_ID_MAX;
 | 
						|
		ut_ad(undo->empty());
 | 
						|
	}
 | 
						|
 | 
						|
	undo->state = state;
 | 
						|
 | 
						|
	if (state != TRX_UNDO_CACHED) {
 | 
						|
		UT_LIST_ADD_LAST(rseg->undo_list, undo);
 | 
						|
	} else {
 | 
						|
		UT_LIST_ADD_LAST(rseg->undo_cached, undo);
 | 
						|
	}
 | 
						|
 | 
						|
	mtr.commit();
 | 
						|
	return undo;
 | 
						|
}
 | 
						|
 | 
						|
/********************************************************************//**
 | 
						|
Creates and initializes an undo log memory object.
 | 
						|
@return own: the undo log memory object */
 | 
						|
static
 | 
						|
trx_undo_t*
 | 
						|
trx_undo_mem_create(
 | 
						|
/*================*/
 | 
						|
	trx_rseg_t*	rseg,	/*!< in: rollback segment memory object */
 | 
						|
	ulint		id,	/*!< in: slot index within rseg */
 | 
						|
	trx_id_t	trx_id,	/*!< in: id of the trx for which the undo log
 | 
						|
				is created */
 | 
						|
	const XID*	xid,	/*!< in: X/Open transaction identification */
 | 
						|
	uint32_t	page_no,/*!< in: undo log header page number */
 | 
						|
	uint16_t	offset)	/*!< in: undo log header byte offset on page */
 | 
						|
{
 | 
						|
	trx_undo_t*	undo;
 | 
						|
 | 
						|
	ut_a(id < TRX_RSEG_N_SLOTS);
 | 
						|
 | 
						|
	undo = static_cast<trx_undo_t*>(ut_malloc_nokey(sizeof(*undo)));
 | 
						|
 | 
						|
	if (undo == NULL) {
 | 
						|
 | 
						|
		return(NULL);
 | 
						|
	}
 | 
						|
 | 
						|
	undo->id = id;
 | 
						|
	undo->state = TRX_UNDO_ACTIVE;
 | 
						|
	undo->trx_id = trx_id;
 | 
						|
	undo->xid = *xid;
 | 
						|
 | 
						|
	undo->dict_operation = FALSE;
 | 
						|
 | 
						|
	undo->rseg = rseg;
 | 
						|
 | 
						|
	undo->hdr_page_no = page_no;
 | 
						|
	undo->hdr_offset = offset;
 | 
						|
	undo->last_page_no = page_no;
 | 
						|
	undo->size = 1;
 | 
						|
 | 
						|
	undo->top_undo_no = IB_ID_MAX;
 | 
						|
	undo->top_page_no = page_no;
 | 
						|
	undo->guess_block = NULL;
 | 
						|
	ut_ad(undo->empty());
 | 
						|
 | 
						|
	return(undo);
 | 
						|
}
 | 
						|
 | 
						|
/********************************************************************//**
 | 
						|
Initializes a cached undo log object for new use. */
 | 
						|
static
 | 
						|
void
 | 
						|
trx_undo_mem_init_for_reuse(
 | 
						|
/*========================*/
 | 
						|
	trx_undo_t*	undo,	/*!< in: undo log to init */
 | 
						|
	trx_id_t	trx_id,	/*!< in: id of the trx for which the undo log
 | 
						|
				is created */
 | 
						|
	const XID*	xid,	/*!< in: X/Open XA transaction identification*/
 | 
						|
	uint16_t	offset)	/*!< in: undo log header byte offset on page */
 | 
						|
{
 | 
						|
	ut_a(undo->id < TRX_RSEG_N_SLOTS);
 | 
						|
 | 
						|
	undo->state = TRX_UNDO_ACTIVE;
 | 
						|
	undo->trx_id = trx_id;
 | 
						|
	undo->xid = *xid;
 | 
						|
 | 
						|
	undo->dict_operation = FALSE;
 | 
						|
 | 
						|
	undo->hdr_offset = offset;
 | 
						|
	undo->top_undo_no = IB_ID_MAX;
 | 
						|
	ut_ad(undo->empty());
 | 
						|
}
 | 
						|
 | 
						|
/** Create an undo log.
 | 
						|
@param[in,out]	trx	transaction
 | 
						|
@param[in,out]	rseg	rollback segment
 | 
						|
@param[out]	undo	undo log object
 | 
						|
@param[out]	err	error code
 | 
						|
@param[in,out]	mtr	mini-transaction
 | 
						|
@return undo log block
 | 
						|
@retval	NULL	on failure */
 | 
						|
static MY_ATTRIBUTE((nonnull, warn_unused_result))
 | 
						|
buf_block_t*
 | 
						|
trx_undo_create(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** undo,
 | 
						|
		dberr_t* err, mtr_t* mtr)
 | 
						|
{
 | 
						|
	ulint		id;
 | 
						|
	buf_block_t*	block = rseg->get(mtr, err);
 | 
						|
 | 
						|
	if (block) {
 | 
						|
		block = trx_undo_seg_create(rseg->space, block, &id, err, mtr);
 | 
						|
	}
 | 
						|
 | 
						|
	if (!block) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	rseg->curr_size++;
 | 
						|
 | 
						|
	uint16_t offset = trx_undo_header_create(block, trx->id, mtr);
 | 
						|
 | 
						|
	*undo = trx_undo_mem_create(rseg, id, trx->id, &trx->xid,
 | 
						|
				    block->page.id().page_no(), offset);
 | 
						|
	if (*undo == NULL) {
 | 
						|
		*err = DB_OUT_OF_MEMORY;
 | 
						|
		 /* FIXME: this will not free the undo block to the file */
 | 
						|
		return NULL;
 | 
						|
	} else if (rseg != trx->rsegs.m_redo.rseg) {
 | 
						|
		return block;
 | 
						|
	}
 | 
						|
 | 
						|
	if (trx->dict_operation) {
 | 
						|
		(*undo)->dict_operation = true;
 | 
						|
		mtr->write<1,mtr_t::MAYBE_NOP>(*block,
 | 
						|
					       block->page.frame + offset
 | 
						|
					       + TRX_UNDO_DICT_TRANS, 1U);
 | 
						|
		mtr->write<8,mtr_t::MAYBE_NOP>(*block,
 | 
						|
					       block->page.frame + offset
 | 
						|
					       + TRX_UNDO_TABLE_ID, 0U);
 | 
						|
	}
 | 
						|
 | 
						|
	*err = DB_SUCCESS;
 | 
						|
	return block;
 | 
						|
}
 | 
						|
 | 
						|
/*================ UNDO LOG ASSIGNMENT AND CLEANUP =====================*/
 | 
						|
 | 
						|
/** Reuse a cached undo log block.
 | 
						|
@param[in,out]	trx	transaction
 | 
						|
@param[in,out]	rseg	rollback segment
 | 
						|
@param[out]	pundo	the undo log memory object
 | 
						|
@param[in,out]	mtr	mini-transaction
 | 
						|
@param[out]	err	error code
 | 
						|
@return	the undo log block
 | 
						|
@retval	NULL	if none cached */
 | 
						|
static
 | 
						|
buf_block_t*
 | 
						|
trx_undo_reuse_cached(trx_t* trx, trx_rseg_t* rseg, trx_undo_t** pundo,
 | 
						|
		      mtr_t* mtr, dberr_t *err)
 | 
						|
{
 | 
						|
	ut_ad(rseg->is_persistent());
 | 
						|
	ut_ad(rseg->is_referenced());
 | 
						|
	ut_ad(rseg == trx->rsegs.m_redo.rseg);
 | 
						|
 | 
						|
	if (rseg->needs_purge <= trx->id) {
 | 
						|
		/* trx_purge_truncate_history() checks
 | 
						|
		purge_sys.sees(rseg.needs_purge)
 | 
						|
		so we need to compensate for that.
 | 
						|
		The rseg->needs_purge after crash
 | 
						|
		recovery would be at least trx->id + 1,
 | 
						|
		because that is the minimum possible value
 | 
						|
		assigned by trx_serialise() on commit. */
 | 
						|
		rseg->needs_purge = trx->id + 1;
 | 
						|
	}
 | 
						|
 | 
						|
	trx_undo_t* undo = UT_LIST_GET_FIRST(rseg->undo_cached);
 | 
						|
	if (!undo) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	ut_ad(undo->size == 1);
 | 
						|
	ut_ad(undo->id < TRX_RSEG_N_SLOTS);
 | 
						|
 | 
						|
	buf_block_t* block = buf_page_get_gen(page_id_t(undo->rseg->space->id,
 | 
						|
							undo->hdr_page_no),
 | 
						|
					      0, RW_X_LATCH, nullptr, BUF_GET,
 | 
						|
					      mtr, err);
 | 
						|
	if (!block) {
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	buf_page_make_young_if_needed(&block->page);
 | 
						|
 | 
						|
	UT_LIST_REMOVE(rseg->undo_cached, undo);
 | 
						|
 | 
						|
	*pundo = undo;
 | 
						|
 | 
						|
	uint16_t offset = trx_undo_header_create(block, trx->id, mtr);
 | 
						|
 | 
						|
	trx_undo_mem_init_for_reuse(undo, trx->id, &trx->xid, offset);
 | 
						|
 | 
						|
	if (trx->dict_operation) {
 | 
						|
		undo->dict_operation = TRUE;
 | 
						|
		mtr->write<1,mtr_t::MAYBE_NOP>(*block,
 | 
						|
					       block->page.frame + offset
 | 
						|
					       + TRX_UNDO_DICT_TRANS, 1U);
 | 
						|
		mtr->write<8,mtr_t::MAYBE_NOP>(*block,
 | 
						|
					       block->page.frame + offset
 | 
						|
					       + TRX_UNDO_TABLE_ID, 0U);
 | 
						|
	}
 | 
						|
 | 
						|
	return block;
 | 
						|
}
 | 
						|
 | 
						|
/** Assign an undo log for a persistent transaction.
 | 
						|
A new undo log is created or a cached undo log reused.
 | 
						|
@param[in,out]	trx	transaction
 | 
						|
@param[out]	err	error code
 | 
						|
@param[in,out]	mtr	mini-transaction
 | 
						|
@return	the undo log block
 | 
						|
@retval	NULL	on error */
 | 
						|
buf_block_t*
 | 
						|
trx_undo_assign(trx_t* trx, dberr_t* err, mtr_t* mtr)
 | 
						|
{
 | 
						|
	ut_ad(mtr->get_log_mode() == MTR_LOG_ALL);
 | 
						|
 | 
						|
	trx_undo_t* undo = trx->rsegs.m_redo.undo;
 | 
						|
	buf_block_t* block;
 | 
						|
 | 
						|
	if (undo) {
 | 
						|
		block = buf_page_get_gen(
 | 
						|
			page_id_t(undo->rseg->space->id, undo->last_page_no),
 | 
						|
			0, RW_X_LATCH, undo->guess_block,
 | 
						|
			BUF_GET, mtr, err);
 | 
						|
		if (UNIV_LIKELY(block != nullptr)) {
 | 
						|
			buf_page_make_young_if_needed(&block->page);
 | 
						|
		}
 | 
						|
		return block;
 | 
						|
	}
 | 
						|
 | 
						|
	*err = DB_SUCCESS;
 | 
						|
	trx_rseg_t* rseg = trx->rsegs.m_redo.rseg;
 | 
						|
 | 
						|
	rseg->latch.wr_lock(SRW_LOCK_CALL);
 | 
						|
	block = trx_undo_reuse_cached(
 | 
						|
		trx, rseg, &trx->rsegs.m_redo.undo, mtr, err);
 | 
						|
 | 
						|
	if (!block) {
 | 
						|
		block = trx_undo_create(trx, rseg, &trx->rsegs.m_redo.undo,
 | 
						|
					err, mtr);
 | 
						|
		ut_ad(!block == (*err != DB_SUCCESS));
 | 
						|
		if (!block) {
 | 
						|
			goto func_exit;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	UT_LIST_ADD_FIRST(rseg->undo_list, trx->rsegs.m_redo.undo);
 | 
						|
 | 
						|
func_exit:
 | 
						|
	rseg->latch.wr_unlock();
 | 
						|
	return block;
 | 
						|
}
 | 
						|
 | 
						|
/** Assign an undo log for a transaction.
 | 
						|
A new undo log is created or a cached undo log reused.
 | 
						|
@tparam is_temp  whether this is temporary undo log
 | 
						|
@param[in,out]	trx	transaction
 | 
						|
@param[in]	rseg	rollback segment
 | 
						|
@param[out]	undo	the undo log
 | 
						|
@param[in,out]	mtr	mini-transaction
 | 
						|
@param[out]	err	error code
 | 
						|
@return	the undo log block
 | 
						|
@retval	nullptr	on error */
 | 
						|
template<bool is_temp>
 | 
						|
buf_block_t*
 | 
						|
trx_undo_assign_low(trx_t *trx, trx_rseg_t *rseg, trx_undo_t **undo,
 | 
						|
                    mtr_t *mtr, dberr_t *err)
 | 
						|
{
 | 
						|
	ut_ad(is_temp == (rseg == trx->rsegs.m_noredo.rseg));
 | 
						|
	ut_ad(is_temp || rseg == trx->rsegs.m_redo.rseg);
 | 
						|
	ut_ad(undo == (is_temp
 | 
						|
		       ? &trx->rsegs.m_noredo.undo
 | 
						|
		       : &trx->rsegs.m_redo.undo));
 | 
						|
	ut_ad(mtr->get_log_mode()
 | 
						|
	      == (is_temp ? MTR_LOG_NO_REDO : MTR_LOG_ALL));
 | 
						|
	buf_block_t* block;
 | 
						|
 | 
						|
	if (*undo) {
 | 
						|
		block = buf_page_get_gen(
 | 
						|
			page_id_t(rseg->space->id, (*undo)->last_page_no),
 | 
						|
			0, RW_X_LATCH, (*undo)->guess_block,
 | 
						|
			BUF_GET, mtr, err);
 | 
						|
		if (UNIV_LIKELY(block != nullptr)) {
 | 
						|
			buf_page_make_young_if_needed(&block->page);
 | 
						|
		}
 | 
						|
		return block;
 | 
						|
	}
 | 
						|
 | 
						|
	DBUG_EXECUTE_IF(
 | 
						|
		"ib_create_table_fail_too_many_trx",
 | 
						|
		*err = DB_TOO_MANY_CONCURRENT_TRXS; return NULL;
 | 
						|
	);
 | 
						|
 | 
						|
	*err = DB_SUCCESS;
 | 
						|
	rseg->latch.wr_lock(SRW_LOCK_CALL);
 | 
						|
	if (is_temp) {
 | 
						|
		ut_ad(!UT_LIST_GET_LEN(rseg->undo_cached));
 | 
						|
	} else {
 | 
						|
		block = trx_undo_reuse_cached(trx, rseg, undo, mtr, err);
 | 
						|
		if (block) {
 | 
						|
			goto got_block;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	block = trx_undo_create(trx, rseg, undo, err, mtr);
 | 
						|
	ut_ad(!block == (*err != DB_SUCCESS));
 | 
						|
	if (!block) {
 | 
						|
		goto func_exit;
 | 
						|
	}
 | 
						|
 | 
						|
got_block:
 | 
						|
	UT_LIST_ADD_FIRST(rseg->undo_list, *undo);
 | 
						|
 | 
						|
func_exit:
 | 
						|
	rseg->latch.wr_unlock();
 | 
						|
	return block;
 | 
						|
}
 | 
						|
 | 
						|
template buf_block_t*
 | 
						|
trx_undo_assign_low<false>(trx_t *trx, trx_rseg_t *rseg, trx_undo_t **undo,
 | 
						|
                           mtr_t *mtr, dberr_t *err);
 | 
						|
template buf_block_t*
 | 
						|
trx_undo_assign_low<true>(trx_t *trx, trx_rseg_t *rseg, trx_undo_t **undo,
 | 
						|
                          mtr_t *mtr, dberr_t *err);
 | 
						|
 | 
						|
/** Set the state of the undo log segment at a XA PREPARE or XA ROLLBACK.
 | 
						|
@param[in,out]	trx		transaction
 | 
						|
@param[in,out]	undo		undo log
 | 
						|
@param[in]	rollback	false=XA PREPARE, true=XA ROLLBACK
 | 
						|
@param[in,out]	mtr		mini-transaction
 | 
						|
@return undo log segment header page, x-latched */
 | 
						|
void trx_undo_set_state_at_prepare(trx_t *trx, trx_undo_t *undo, bool rollback,
 | 
						|
				   mtr_t *mtr)
 | 
						|
{
 | 
						|
	ut_a(undo->id < TRX_RSEG_N_SLOTS);
 | 
						|
 | 
						|
	buf_block_t* block = buf_page_get(
 | 
						|
		page_id_t(undo->rseg->space->id, undo->hdr_page_no), 0,
 | 
						|
		RW_X_LATCH, mtr);
 | 
						|
	if (UNIV_UNLIKELY(!block)) {
 | 
						|
		/* In case of !rollback the undo header page
 | 
						|
		corruption would leave the transaction object in an
 | 
						|
		unexpected (active) state. */
 | 
						|
		ut_a(rollback);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (rollback) {
 | 
						|
		ut_ad(undo->state == TRX_UNDO_PREPARED);
 | 
						|
		mtr->write<2>(*block, TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
 | 
						|
			      + block->page.frame, TRX_UNDO_ACTIVE);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/*------------------------------*/
 | 
						|
	ut_ad(undo->state == TRX_UNDO_ACTIVE);
 | 
						|
	undo->state = TRX_UNDO_PREPARED;
 | 
						|
	undo->xid   = trx->xid;
 | 
						|
	/*------------------------------*/
 | 
						|
 | 
						|
	mtr->write<2>(*block, TRX_UNDO_SEG_HDR + TRX_UNDO_STATE
 | 
						|
		      + block->page.frame, undo->state);
 | 
						|
	uint16_t offset = mach_read_from_2(TRX_UNDO_SEG_HDR + TRX_UNDO_LAST_LOG
 | 
						|
					   + block->page.frame);
 | 
						|
	mtr->write<1>(*block, block->page.frame + offset + TRX_UNDO_XID_EXISTS,
 | 
						|
		      1U);
 | 
						|
 | 
						|
	trx_undo_write_xid(block, offset, undo->xid, mtr);
 | 
						|
}
 | 
						|
 | 
						|
/** At shutdown, frees the undo logs of a transaction. */
 | 
						|
void trx_undo_free_at_shutdown(trx_t *trx)
 | 
						|
{
 | 
						|
	if (trx_undo_t*& undo = trx->rsegs.m_redo.undo) {
 | 
						|
		switch (undo->state) {
 | 
						|
		case TRX_UNDO_PREPARED:
 | 
						|
			break;
 | 
						|
		case TRX_UNDO_CACHED:
 | 
						|
		case TRX_UNDO_TO_PURGE:
 | 
						|
			ut_ad(trx_state_eq(trx,
 | 
						|
					   TRX_STATE_COMMITTED_IN_MEMORY));
 | 
						|
			/* fall through */
 | 
						|
		case TRX_UNDO_ACTIVE:
 | 
						|
			/* trx_t::commit_state() assigns
 | 
						|
			trx->state = TRX_STATE_COMMITTED_IN_MEMORY. */
 | 
						|
			ut_a(!srv_was_started
 | 
						|
			     || srv_read_only_mode
 | 
						|
			     || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO
 | 
						|
			     || srv_fast_shutdown);
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			ut_error;
 | 
						|
		}
 | 
						|
 | 
						|
		UT_LIST_REMOVE(trx->rsegs.m_redo.rseg->undo_list, undo);
 | 
						|
		ut_free(undo);
 | 
						|
		undo = NULL;
 | 
						|
	}
 | 
						|
	if (trx_undo_t*& undo = trx->rsegs.m_noredo.undo) {
 | 
						|
		ut_a(undo->state == TRX_UNDO_PREPARED);
 | 
						|
 | 
						|
		UT_LIST_REMOVE(trx->rsegs.m_noredo.rseg->undo_list, undo);
 | 
						|
		ut_free(undo);
 | 
						|
		undo = NULL;
 | 
						|
	}
 | 
						|
}
 |