/*****************************************************************************

Copyright (c) 1996, 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 trx/trx0sys.cc
Transaction system

Created 3/26/1996 Heikki Tuuri
*******************************************************/

#include "trx0sys.h"
#include "mysqld.h"
#include "sql_error.h"

#include "fsp0fsp.h"
#include "mtr0log.h"
#include "mtr0log.h"
#include "trx0trx.h"
#include "trx0rseg.h"
#include "trx0undo.h"
#include "srv0srv.h"
#include "srv0start.h"
#include "trx0purge.h"
#include "log0log.h"
#include "log0recv.h"
#include "os0file.h"

/** The transaction system */
trx_sys_t		trx_sys;

/** Check whether transaction id is valid.
@param[in]	id              transaction id to check
@param[in]      name            table name */
void
ReadViewBase::check_trx_id_sanity(
	trx_id_t		id,
	const table_name_t&	name)
{
	if (id >= trx_sys.get_max_trx_id()) {

		ib::warn() << "A transaction id"
			   << " in a record of table "
			   << name
			   << " is newer than the"
			   << " system-wide maximum.";
		ut_ad(0);
		THD *thd = current_thd;
		if (thd != NULL) {
			char    table_name[MAX_FULL_NAME_LEN + 1];

			innobase_format_name(
				table_name, sizeof(table_name),
				name.m_name);

			push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
					    ER_SIGNAL_WARN,
					    "InnoDB: Transaction id"
					    " in a record of table"
					    " %s is newer than system-wide"
					    " maximum.", table_name);
		}
	}
}

#ifdef UNIV_DEBUG
/* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */
uint	trx_rseg_n_slots_debug = 0;
#endif

/** Display the MySQL binlog offset info if it is present in the trx
system header. */
void
trx_sys_print_mysql_binlog_offset()
{
	if (!*trx_sys.recovered_binlog_filename) {
		return;
	}

	ib::info() << "Last binlog file '"
		<< trx_sys.recovered_binlog_filename
		<< "', position "
		<< trx_sys.recovered_binlog_offset;
}

/** Find an available rollback segment.
@param[in]	sys_header
@return an unallocated rollback segment slot in the TRX_SYS header
@retval ULINT_UNDEFINED if not found */
ulint
trx_sys_rseg_find_free(const buf_block_t* sys_header)
{
	for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) {
		if (trx_sysf_rseg_get_page_no(sys_header, rseg_id)
		    == FIL_NULL) {
			return rseg_id;
		}
	}

	return(ULINT_UNDEFINED);
}

/** Count the number of initialized persistent rollback segment slots. */
static
void
trx_sysf_get_n_rseg_slots()
{
	mtr_t		mtr;
	mtr.start();

	srv_available_undo_logs = 0;
	if (const buf_block_t* sys_header = trx_sysf_get(&mtr, false)) {
		for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) {
			srv_available_undo_logs
				+= trx_sysf_rseg_get_page_no(sys_header,
							     rseg_id)
				!= FIL_NULL;
		}
	}

	mtr.commit();
}

/*****************************************************************//**
Creates the file page for the transaction system. This function is called only
at the database creation, before trx_sys_init. */
static
void
trx_sysf_create(
/*============*/
	mtr_t*	mtr)	/*!< in: mtr */
{
	ulint		slot_no;
	buf_block_t*	block;

	ut_ad(mtr);

	/* Note that below we first reserve the file space x-latch, and
	then enter the kernel: we must do it in this order to conform
	to the latching order rules. */

	mtr_x_lock_space(fil_system.sys_space, mtr);
	compile_time_assert(TRX_SYS_SPACE == 0);

	/* Create the trx sys file block in a new allocated file segment */
	block = fseg_create(fil_system.sys_space,
			    TRX_SYS + TRX_SYS_FSEG_HEADER,
			    mtr);
	buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER);

	ut_a(block->page.id() == page_id_t(0, TRX_SYS_PAGE_NO));

	mtr->write<2>(*block, FIL_PAGE_TYPE + block->frame,
		      FIL_PAGE_TYPE_TRX_SYS);

	ut_ad(!mach_read_from_4(block->frame
				+ TRX_SYS_DOUBLEWRITE
				+ TRX_SYS_DOUBLEWRITE_MAGIC));

	/* Reset the rollback segment slots.  Old versions of InnoDB
	(before MySQL 5.5) define TRX_SYS_N_RSEGS as 256 and expect
	that the whole array is initialized. */
	compile_time_assert(256 >= TRX_SYS_N_RSEGS);
	compile_time_assert(TRX_SYS + TRX_SYS_RSEGS
			    + 256 * TRX_SYS_RSEG_SLOT_SIZE
			    <= UNIV_PAGE_SIZE_MIN - FIL_PAGE_DATA_END);
	mtr->memset(block, TRX_SYS + TRX_SYS_RSEGS,
		    256 * TRX_SYS_RSEG_SLOT_SIZE, 0xff);
	/* Initialize all of the page.  This part used to be uninitialized. */
	mtr->memset(block, TRX_SYS + TRX_SYS_RSEGS
		    + 256 * TRX_SYS_RSEG_SLOT_SIZE,
		    srv_page_size
		    - (FIL_PAGE_DATA_END + TRX_SYS + TRX_SYS_RSEGS
		       + 256 * TRX_SYS_RSEG_SLOT_SIZE),
		    0);

	/* Create the first rollback segment in the SYSTEM tablespace */
	slot_no = trx_sys_rseg_find_free(block);
	buf_block_t* rblock = trx_rseg_header_create(fil_system.sys_space,
						     slot_no, block, mtr);

	ut_a(slot_no == TRX_SYS_SYSTEM_RSEG_ID);
	ut_a(rblock->page.id() == page_id_t(0, FSP_FIRST_RSEG_PAGE_NO));
}

/** Create the instance */
void
trx_sys_t::create()
{
	ut_ad(this == &trx_sys);
	ut_ad(!is_initialised());
	m_initialised = true;
	trx_list.create();
	rseg_history_len= 0;

	rw_trx_hash.init();
}

/*****************************************************************//**
Creates and initializes the transaction system at the database creation. */
void
trx_sys_create_sys_pages(void)
/*==========================*/
{
	mtr_t	mtr;

	mtr_start(&mtr);

	trx_sysf_create(&mtr);

	mtr_commit(&mtr);
}

/** Create the rollback segments.
@return	whether the creation succeeded */
bool
trx_sys_create_rsegs()
{
	/* srv_available_undo_logs reflects the number of persistent
	rollback segments that have been initialized in the
	transaction system header page. */
	ut_ad(srv_undo_tablespaces <= TRX_SYS_MAX_UNDO_SPACES);

	if (high_level_read_only) {
		srv_available_undo_logs = 0;
		return(true);
	}

	/* This is executed in single-threaded mode therefore it is not
	necessary to use the same mtr in trx_rseg_create(). n_used cannot
	change while the function is executing. */
	trx_sysf_get_n_rseg_slots();

	ut_ad(srv_available_undo_logs <= TRX_SYS_N_RSEGS);

	/* The first persistent rollback segment is always initialized
	in the system tablespace. */
	ut_a(srv_available_undo_logs > 0);

	for (ulint i = 0; srv_available_undo_logs < TRX_SYS_N_RSEGS;
	     i++, srv_available_undo_logs++) {
		/* Tablespace 0 is the system tablespace.
		Dedicated undo log tablespaces start from 1. */
		ulint space = srv_undo_tablespaces > 0
			? (i % srv_undo_tablespaces)
			+ srv_undo_space_id_start
			: TRX_SYS_SPACE;

		if (!trx_rseg_create(space)) {
			ib::error() << "Unable to allocate the"
				" requested innodb_undo_logs";
			return(false);
		}

		/* Increase the number of active undo
		tablespace in case new rollback segment
		assigned to new undo tablespace. */
		if (space > srv_undo_tablespaces_active) {
			srv_undo_tablespaces_active++;

			ut_ad(srv_undo_tablespaces_active == space);
		}
	}

	ut_ad(srv_available_undo_logs == TRX_SYS_N_RSEGS);

	ib::info info;
	info << srv_available_undo_logs;
	if (srv_undo_tablespaces_active) {
		info << " rollback segments in " << srv_undo_tablespaces_active
		<< " undo tablespaces are active.";
	} else {
		info << " rollback segments are active.";
	}

	return(true);
}

/** Close the transaction system on shutdown */
void
trx_sys_t::close()
{
	ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS);
	if (!is_initialised()) {
		return;
	}

	if (size_t size = view_count()) {
		ib::error() << "All read views were not closed before"
			" shutdown: " << size << " read views open";
	}

	rw_trx_hash.destroy();

	/* There can't be any active transactions. */

	for (ulint i = 0; i < TRX_SYS_N_RSEGS; ++i) {
		if (trx_rseg_t* rseg = rseg_array[i]) {
			trx_rseg_mem_free(rseg);
		}

		if (trx_rseg_t* rseg = temp_rsegs[i]) {
			trx_rseg_mem_free(rseg);
		}
	}

	ut_a(trx_list.empty());
	trx_list.close();
	m_initialised = false;
}

/** @return total number of active (non-prepared) transactions */
ulint trx_sys_t::any_active_transactions()
{
  uint32_t total_trx= 0;

  trx_sys.trx_list.for_each([&total_trx](const trx_t &trx) {
    if (trx.state == TRX_STATE_COMMITTED_IN_MEMORY ||
        (trx.state == TRX_STATE_ACTIVE && trx.id))
      total_trx++;
  });

  return total_trx;
}
