mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-03 20:36:16 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			779 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			779 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*****************************************************************************
 | 
						|
 | 
						|
Copyright (c) 2011, 2017, Oracle and/or its affiliates. All Rights Reserved.
 | 
						|
Copyright (c) 2017, 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 buf/buf0dump.cc
 | 
						|
Implements a buffer pool dump/load.
 | 
						|
 | 
						|
Created April 08, 2011 Vasil Dimov
 | 
						|
*******************************************************/
 | 
						|
 | 
						|
#include "my_global.h"
 | 
						|
#include "mysqld.h"
 | 
						|
#include "my_sys.h"
 | 
						|
 | 
						|
#include "mysql/psi/mysql_stage.h"
 | 
						|
#include "mysql/psi/psi.h"
 | 
						|
 | 
						|
#include "buf0rea.h"
 | 
						|
#include "buf0dump.h"
 | 
						|
#include "dict0load.h"
 | 
						|
#include "os0file.h"
 | 
						|
#include "srv0srv.h"
 | 
						|
#include "srv0start.h"
 | 
						|
#include "ut0byte.h"
 | 
						|
 | 
						|
#include <algorithm>
 | 
						|
 | 
						|
#include "mysql/service_wsrep.h" /* wsrep_recovery */
 | 
						|
#include <my_service_manager.h>
 | 
						|
 | 
						|
static void buf_do_load_dump();
 | 
						|
 | 
						|
enum status_severity {
 | 
						|
	STATUS_INFO,
 | 
						|
	STATUS_ERR
 | 
						|
};
 | 
						|
 | 
						|
#define SHUTTING_DOWN()	(srv_shutdown_state != SRV_SHUTDOWN_NONE)
 | 
						|
 | 
						|
/* Flags that tell the buffer pool dump/load thread which action should it
 | 
						|
take after being waked up. */
 | 
						|
static volatile bool	buf_dump_should_start;
 | 
						|
static volatile bool	buf_load_should_start;
 | 
						|
 | 
						|
static Atomic_relaxed<bool> buf_load_abort_flag;
 | 
						|
 | 
						|
/** Start the buffer pool dump/load task and instructs it to start a dump. */
 | 
						|
void buf_dump_start()
 | 
						|
{
 | 
						|
  buf_dump_should_start= true;
 | 
						|
  buf_do_load_dump();
 | 
						|
}
 | 
						|
 | 
						|
/** Start the buffer pool dump/load task and instructs it to start a load. */
 | 
						|
void buf_load_start()
 | 
						|
{
 | 
						|
  buf_load_should_start= true;
 | 
						|
  buf_do_load_dump();
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
Sets the global variable that feeds MySQL's innodb_buffer_pool_dump_status
 | 
						|
to the specified string. The format and the following parameters are the
 | 
						|
same as the ones used for printf(3). The value of this variable can be
 | 
						|
retrieved by:
 | 
						|
SELECT variable_value FROM information_schema.global_status WHERE
 | 
						|
variable_name = 'INNODB_BUFFER_POOL_DUMP_STATUS';
 | 
						|
or by:
 | 
						|
SHOW STATUS LIKE 'innodb_buffer_pool_dump_status'; */
 | 
						|
static MY_ATTRIBUTE((nonnull, format(printf, 2, 3)))
 | 
						|
void
 | 
						|
buf_dump_status(
 | 
						|
/*============*/
 | 
						|
	enum status_severity	severity,/*!< in: status severity */
 | 
						|
	const char*		fmt,	/*!< in: format */
 | 
						|
	...)				/*!< in: extra parameters according
 | 
						|
					to fmt */
 | 
						|
{
 | 
						|
	va_list	ap;
 | 
						|
 | 
						|
	va_start(ap, fmt);
 | 
						|
 | 
						|
	vsnprintf(
 | 
						|
		export_vars.innodb_buffer_pool_dump_status,
 | 
						|
		sizeof(export_vars.innodb_buffer_pool_dump_status),
 | 
						|
		fmt, ap);
 | 
						|
 | 
						|
	switch (severity) {
 | 
						|
	case STATUS_INFO:
 | 
						|
		ib::info() << export_vars.innodb_buffer_pool_dump_status;
 | 
						|
		break;
 | 
						|
 | 
						|
	case STATUS_ERR:
 | 
						|
		ib::error() << export_vars.innodb_buffer_pool_dump_status;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	va_end(ap);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
Sets the global variable that feeds MySQL's innodb_buffer_pool_load_status
 | 
						|
to the specified string. The format and the following parameters are the
 | 
						|
same as the ones used for printf(3). The value of this variable can be
 | 
						|
retrieved by:
 | 
						|
SELECT variable_value FROM information_schema.global_status WHERE
 | 
						|
variable_name = 'INNODB_BUFFER_POOL_LOAD_STATUS';
 | 
						|
or by:
 | 
						|
SHOW STATUS LIKE 'innodb_buffer_pool_load_status'; */
 | 
						|
static MY_ATTRIBUTE((nonnull, format(printf, 2, 3)))
 | 
						|
void
 | 
						|
buf_load_status(
 | 
						|
/*============*/
 | 
						|
	enum status_severity	severity,/*!< in: status severity */
 | 
						|
	const char*	fmt,	/*!< in: format */
 | 
						|
	...)			/*!< in: extra parameters according to fmt */
 | 
						|
{
 | 
						|
	va_list	ap;
 | 
						|
 | 
						|
	va_start(ap, fmt);
 | 
						|
 | 
						|
	vsnprintf(
 | 
						|
		export_vars.innodb_buffer_pool_load_status,
 | 
						|
		sizeof(export_vars.innodb_buffer_pool_load_status),
 | 
						|
		fmt, ap);
 | 
						|
 | 
						|
	switch (severity) {
 | 
						|
	case STATUS_INFO:
 | 
						|
		ib::info() << export_vars.innodb_buffer_pool_load_status;
 | 
						|
		break;
 | 
						|
 | 
						|
	case STATUS_ERR:
 | 
						|
		ib::error() << export_vars.innodb_buffer_pool_load_status;
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	va_end(ap);
 | 
						|
}
 | 
						|
 | 
						|
/** Returns the directory path where the buffer pool dump file will be created.
 | 
						|
@return directory path */
 | 
						|
static
 | 
						|
const char*
 | 
						|
get_buf_dump_dir()
 | 
						|
{
 | 
						|
	const char*	dump_dir;
 | 
						|
 | 
						|
	/* The dump file should be created in the default data directory if
 | 
						|
	innodb_data_home_dir is set as an empty string. */
 | 
						|
	if (!*srv_data_home) {
 | 
						|
		dump_dir = fil_path_to_mysql_datadir;
 | 
						|
	} else {
 | 
						|
		dump_dir = srv_data_home;
 | 
						|
	}
 | 
						|
 | 
						|
	return(dump_dir);
 | 
						|
}
 | 
						|
 | 
						|
/** Generate the path to the buffer pool dump/load file.
 | 
						|
@param[out]	path		generated path
 | 
						|
@param[in]	path_size	size of 'path', used as in snprintf(3). */
 | 
						|
static void buf_dump_generate_path(char *path, size_t path_size)
 | 
						|
{
 | 
						|
	char	buf[FN_REFLEN];
 | 
						|
 | 
						|
	mysql_mutex_lock(&LOCK_global_system_variables);
 | 
						|
	snprintf(buf, sizeof buf, "%s" FN_ROOTDIR "%s", get_buf_dump_dir(),
 | 
						|
		 srv_buf_dump_filename);
 | 
						|
	mysql_mutex_unlock(&LOCK_global_system_variables);
 | 
						|
 | 
						|
	os_file_type_t	type;
 | 
						|
	bool		exists = false;
 | 
						|
	bool		ret;
 | 
						|
 | 
						|
	ret = os_file_status(buf, &exists, &type);
 | 
						|
 | 
						|
	/* For realpath() to succeed the file must exist. */
 | 
						|
 | 
						|
	if (ret && exists) {
 | 
						|
		/* my_realpath() assumes the destination buffer is big enough
 | 
						|
		to hold FN_REFLEN bytes. */
 | 
						|
		ut_a(path_size >= FN_REFLEN);
 | 
						|
 | 
						|
		my_realpath(path, buf, 0);
 | 
						|
	} else {
 | 
						|
		/* If it does not exist, then resolve only srv_data_home
 | 
						|
		and append srv_buf_dump_filename to it. */
 | 
						|
		char	srv_data_home_full[FN_REFLEN];
 | 
						|
 | 
						|
		my_realpath(srv_data_home_full, get_buf_dump_dir(), 0);
 | 
						|
		const char *format;
 | 
						|
 | 
						|
		switch (srv_data_home_full[strlen(srv_data_home_full) - 1]) {
 | 
						|
#ifdef _WIN32
 | 
						|
		case '\\':
 | 
						|
#endif
 | 
						|
		case '/':
 | 
						|
			format = "%s%s";
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			format = "%s" FN_ROOTDIR "%s";
 | 
						|
		}
 | 
						|
 | 
						|
		snprintf(path, path_size, format,
 | 
						|
			 srv_data_home_full, srv_buf_dump_filename);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
Perform a buffer pool dump into the file specified by
 | 
						|
innodb_buffer_pool_filename. If any errors occur then the value of
 | 
						|
innodb_buffer_pool_dump_status will be set accordingly, see buf_dump_status().
 | 
						|
The dump filename can be specified by (relative to srv_data_home):
 | 
						|
SET GLOBAL innodb_buffer_pool_filename='filename'; */
 | 
						|
static
 | 
						|
void
 | 
						|
buf_dump(
 | 
						|
/*=====*/
 | 
						|
	ibool	obey_shutdown)	/*!< in: quit if we are in a shutting down
 | 
						|
				state */
 | 
						|
{
 | 
						|
#define SHOULD_QUIT()	(SHUTTING_DOWN() && obey_shutdown)
 | 
						|
 | 
						|
	char	full_filename[OS_FILE_MAX_PATH];
 | 
						|
	char	tmp_filename[OS_FILE_MAX_PATH + sizeof "incomplete"];
 | 
						|
	char	now[32];
 | 
						|
	FILE*	f;
 | 
						|
	int	ret;
 | 
						|
 | 
						|
	buf_dump_generate_path(full_filename, sizeof(full_filename));
 | 
						|
 | 
						|
	snprintf(tmp_filename, sizeof(tmp_filename),
 | 
						|
		 "%s.incomplete", full_filename);
 | 
						|
 | 
						|
	buf_dump_status(STATUS_INFO, "Dumping buffer pool(s) to %s",
 | 
						|
			full_filename);
 | 
						|
 | 
						|
#ifdef _WIN32
 | 
						|
	/* use my_fopen() for correct permissions during bootstrap*/
 | 
						|
	f = my_fopen(tmp_filename, O_RDWR|O_TRUNC|O_CREAT, 0);
 | 
						|
#elif defined(__GLIBC__) || O_CLOEXEC == 0
 | 
						|
	f = fopen(tmp_filename, "w" STR_O_CLOEXEC);
 | 
						|
#else
 | 
						|
	{
 | 
						|
		int	fd;
 | 
						|
		fd = open(tmp_filename, O_CREAT | O_TRUNC | O_CLOEXEC | O_WRONLY, 0640);
 | 
						|
		if (fd >= 0) {
 | 
						|
			f = fdopen(fd, "w");
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			f = NULL;
 | 
						|
		}
 | 
						|
	}
 | 
						|
#endif
 | 
						|
	if (f == NULL) {
 | 
						|
		buf_dump_status(STATUS_ERR,
 | 
						|
				"Cannot open '%s' for writing: %s",
 | 
						|
				tmp_filename, strerror(errno));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const buf_page_t*	bpage;
 | 
						|
	page_id_t*		dump;
 | 
						|
	ulint			n_pages;
 | 
						|
	ulint			j;
 | 
						|
 | 
						|
	mysql_mutex_lock(&buf_pool.mutex);
 | 
						|
 | 
						|
	n_pages = UT_LIST_GET_LEN(buf_pool.LRU);
 | 
						|
 | 
						|
	/* skip empty buffer pools */
 | 
						|
	if (n_pages == 0) {
 | 
						|
		mysql_mutex_unlock(&buf_pool.mutex);
 | 
						|
		goto done;
 | 
						|
	}
 | 
						|
 | 
						|
	if (srv_buf_pool_dump_pct != 100) {
 | 
						|
		ulint		t_pages;
 | 
						|
 | 
						|
		/* limit the number of total pages dumped to X% of the
 | 
						|
		total number of pages */
 | 
						|
		t_pages = buf_pool.curr_size() * srv_buf_pool_dump_pct / 100;
 | 
						|
		if (n_pages > t_pages) {
 | 
						|
			buf_dump_status(STATUS_INFO,
 | 
						|
					"Restricted to " ULINTPF
 | 
						|
					" pages due to "
 | 
						|
					"innodb_buf_pool_dump_pct=%lu",
 | 
						|
					t_pages, srv_buf_pool_dump_pct);
 | 
						|
			n_pages = t_pages;
 | 
						|
		}
 | 
						|
 | 
						|
		if (n_pages == 0) {
 | 
						|
			n_pages = 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	dump = static_cast<page_id_t*>(ut_malloc_nokey(
 | 
						|
					       n_pages * sizeof(*dump)));
 | 
						|
 | 
						|
	if (dump == NULL) {
 | 
						|
		std::ostringstream str_bytes;
 | 
						|
		mysql_mutex_unlock(&buf_pool.mutex);
 | 
						|
		fclose(f);
 | 
						|
		str_bytes << ib::bytes_iec{n_pages * sizeof(*dump)};
 | 
						|
		buf_dump_status(STATUS_ERR,
 | 
						|
				"Cannot allocate %s: %s",
 | 
						|
				str_bytes.str().c_str(),
 | 
						|
				strerror(errno));
 | 
						|
		/* leave tmp_filename to exist */
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	for (bpage = UT_LIST_GET_FIRST(buf_pool.LRU), j = 0;
 | 
						|
	     bpage != NULL && j < n_pages;
 | 
						|
	     bpage = UT_LIST_GET_NEXT(LRU, bpage)) {
 | 
						|
		const auto status = bpage->state();
 | 
						|
		if (status < buf_page_t::UNFIXED) {
 | 
						|
			ut_a(status >= buf_page_t::FREED);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		const page_id_t id{bpage->id()};
 | 
						|
 | 
						|
		if (id.space() == SRV_TMP_SPACE_ID) {
 | 
						|
			/* Ignore the innodb_temporary tablespace. */
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		dump[j++] = id;
 | 
						|
	}
 | 
						|
 | 
						|
	mysql_mutex_unlock(&buf_pool.mutex);
 | 
						|
 | 
						|
	ut_a(j <= n_pages);
 | 
						|
	n_pages = j;
 | 
						|
 | 
						|
	for (j = 0; j < n_pages && !SHOULD_QUIT(); j++) {
 | 
						|
		ret = fprintf(f, "%u,%u\n",
 | 
						|
			      dump[j].space(), dump[j].page_no());
 | 
						|
		if (ret < 0) {
 | 
						|
			ut_free(dump);
 | 
						|
			fclose(f);
 | 
						|
			buf_dump_status(STATUS_ERR,
 | 
						|
					"Cannot write to '%s': %s",
 | 
						|
					tmp_filename, strerror(errno));
 | 
						|
			/* leave tmp_filename to exist */
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (SHUTTING_DOWN() && !(j & 1023)) {
 | 
						|
			service_manager_extend_timeout(
 | 
						|
				INNODB_EXTEND_TIMEOUT_INTERVAL,
 | 
						|
				"Dumping buffer pool page "
 | 
						|
				ULINTPF "/" ULINTPF, j + 1, n_pages);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	ut_free(dump);
 | 
						|
 | 
						|
done:
 | 
						|
	ret = IF_WIN(my_fclose(f,0),fclose(f));
 | 
						|
	if (ret != 0) {
 | 
						|
		buf_dump_status(STATUS_ERR,
 | 
						|
				"Cannot close '%s': %s",
 | 
						|
				tmp_filename, strerror(errno));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	/* else */
 | 
						|
 | 
						|
	ret = unlink(full_filename);
 | 
						|
	if (ret != 0 && errno != ENOENT) {
 | 
						|
		buf_dump_status(STATUS_ERR,
 | 
						|
				"Cannot delete '%s': %s",
 | 
						|
				full_filename, strerror(errno));
 | 
						|
		/* leave tmp_filename to exist */
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	/* else */
 | 
						|
 | 
						|
	ret = rename(tmp_filename, full_filename);
 | 
						|
	if (ret != 0) {
 | 
						|
		buf_dump_status(STATUS_ERR,
 | 
						|
				"Cannot rename '%s' to '%s': %s",
 | 
						|
				tmp_filename, full_filename,
 | 
						|
				strerror(errno));
 | 
						|
		/* leave tmp_filename to exist */
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	/* else */
 | 
						|
 | 
						|
	/* success */
 | 
						|
 | 
						|
	ut_sprintf_timestamp(now);
 | 
						|
 | 
						|
	buf_dump_status(STATUS_INFO,
 | 
						|
			"Buffer pool(s) dump completed at %s", now);
 | 
						|
 | 
						|
	/* Though dumping doesn't related to an incomplete load,
 | 
						|
	 we reset this to 0 here to indicate that a shutdown can also perform
 | 
						|
	 a dump */
 | 
						|
	export_vars.innodb_buffer_pool_load_incomplete = 0;
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
Perform a buffer pool load from the file specified by
 | 
						|
innodb_buffer_pool_filename. If any errors occur then the value of
 | 
						|
innodb_buffer_pool_load_status will be set accordingly, see buf_load_status().
 | 
						|
The dump filename can be specified by (relative to srv_data_home):
 | 
						|
SET GLOBAL innodb_buffer_pool_filename='filename'; */
 | 
						|
static
 | 
						|
void
 | 
						|
buf_load()
 | 
						|
/*======*/
 | 
						|
{
 | 
						|
	char		full_filename[OS_FILE_MAX_PATH];
 | 
						|
	char		now[32];
 | 
						|
	FILE*		f;
 | 
						|
	page_id_t*	dump;
 | 
						|
	ulint		dump_n;
 | 
						|
	ulint		i;
 | 
						|
	uint32_t	space_id;
 | 
						|
	uint32_t	page_no;
 | 
						|
	int		fscanf_ret;
 | 
						|
 | 
						|
	/* Ignore any leftovers from before */
 | 
						|
	buf_load_abort_flag = false;
 | 
						|
 | 
						|
	buf_dump_generate_path(full_filename, sizeof(full_filename));
 | 
						|
 | 
						|
	buf_load_status(STATUS_INFO,
 | 
						|
			"Loading buffer pool(s) from %s", full_filename);
 | 
						|
 | 
						|
	f = fopen(full_filename, "r" STR_O_CLOEXEC);
 | 
						|
	if (f == NULL) {
 | 
						|
		buf_load_status(STATUS_INFO,
 | 
						|
				"Cannot open '%s' for reading: %s",
 | 
						|
				full_filename, strerror(errno));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	/* else */
 | 
						|
 | 
						|
	/* First scan the file to estimate how many entries are in it.
 | 
						|
	This file is tiny (approx 500KB per 1GB buffer pool), reading it
 | 
						|
	two times is fine. */
 | 
						|
	dump_n = 0;
 | 
						|
	while (fscanf(f, "%u,%u", &space_id, &page_no) == 2
 | 
						|
	       && !SHUTTING_DOWN()) {
 | 
						|
		dump_n++;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!SHUTTING_DOWN() && !feof(f)) {
 | 
						|
		/* fscanf() returned != 2 */
 | 
						|
		const char*	what;
 | 
						|
		if (ferror(f)) {
 | 
						|
			what = "reading";
 | 
						|
		} else {
 | 
						|
			what = "parsing";
 | 
						|
		}
 | 
						|
		fclose(f);
 | 
						|
		buf_load_status(STATUS_ERR, "Error %s '%s',"
 | 
						|
				" unable to load buffer pool (stage 1)",
 | 
						|
				what, full_filename);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If the dump is larger than the buffer pool, then we ignore the
 | 
						|
	extra trailing. This could happen if a dump is made, then buffer
 | 
						|
	pool is shrunk and then load is attempted. */
 | 
						|
	dump_n = std::min(dump_n, buf_pool.curr_size());
 | 
						|
 | 
						|
	if (dump_n != 0) {
 | 
						|
		dump = static_cast<page_id_t*>(ut_malloc_nokey(
 | 
						|
				dump_n * sizeof(*dump)));
 | 
						|
	} else {
 | 
						|
		fclose(f);
 | 
						|
		ut_sprintf_timestamp(now);
 | 
						|
		buf_load_status(STATUS_INFO,
 | 
						|
				"Buffer pool(s) load completed at %s"
 | 
						|
				" (%s was empty)", now, full_filename);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (dump == NULL) {
 | 
						|
		std::ostringstream str_bytes;
 | 
						|
		fclose(f);
 | 
						|
		str_bytes << ib::bytes_iec{dump_n * sizeof(*dump)};
 | 
						|
		buf_dump_status(STATUS_ERR,
 | 
						|
				"Cannot allocate %s: %s",
 | 
						|
				str_bytes.str().c_str(),
 | 
						|
				strerror(errno));
 | 
						|
		/* leave tmp_filename to exist */
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	rewind(f);
 | 
						|
 | 
						|
	export_vars.innodb_buffer_pool_load_incomplete = 1;
 | 
						|
 | 
						|
	for (i = 0; i < dump_n && !SHUTTING_DOWN(); i++) {
 | 
						|
		fscanf_ret = fscanf(f, "%u,%u", &space_id, &page_no);
 | 
						|
 | 
						|
		if (fscanf_ret != 2) {
 | 
						|
			if (feof(f)) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
			/* else */
 | 
						|
 | 
						|
			ut_free(dump);
 | 
						|
			fclose(f);
 | 
						|
			buf_load_status(STATUS_ERR,
 | 
						|
					"Error parsing '%s', unable"
 | 
						|
					" to load buffer pool (stage 2)",
 | 
						|
					full_filename);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		if (space_id > ULINT32_MASK || page_no > ULINT32_MASK) {
 | 
						|
			ut_free(dump);
 | 
						|
			fclose(f);
 | 
						|
			buf_load_status(STATUS_ERR,
 | 
						|
					"Error parsing '%s': bogus"
 | 
						|
					" space,page %u,%u at line " ULINTPF
 | 
						|
					", unable to load buffer pool",
 | 
						|
					full_filename,
 | 
						|
					space_id, page_no,
 | 
						|
					i);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		dump[i] = page_id_t(space_id, page_no);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Set dump_n to the actual number of initialized elements,
 | 
						|
	i could be smaller than dump_n here if the file got truncated after
 | 
						|
	we read it the first time. */
 | 
						|
	dump_n = i;
 | 
						|
 | 
						|
	fclose(f);
 | 
						|
 | 
						|
	if (dump_n == 0) {
 | 
						|
		ut_free(dump);
 | 
						|
		ut_sprintf_timestamp(now);
 | 
						|
		buf_load_status(STATUS_INFO,
 | 
						|
				"Buffer pool(s) load completed at %s"
 | 
						|
				" (%s was empty or had errors)", now, full_filename);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!SHUTTING_DOWN()) {
 | 
						|
		std::sort(dump, dump + dump_n);
 | 
						|
		std::set<uint32_t> missing;
 | 
						|
		for (const page_id_t id : st_::span<const page_id_t>
 | 
						|
		       (dump, dump_n)) {
 | 
						|
			missing.emplace(id.space());
 | 
						|
		}
 | 
						|
		for (std::set<uint32_t>::iterator i = missing.begin();
 | 
						|
		     i != missing.end(); ) {
 | 
						|
			auto j = i++;
 | 
						|
			if (fil_space_t* space = fil_space_t::get(*j)) {
 | 
						|
				space->release();
 | 
						|
				missing.erase(j);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if (!missing.empty()) {
 | 
						|
			dict_load_tablespaces(&missing);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* Avoid calling the expensive fil_space_t::get() for each
 | 
						|
	page within the same tablespace. dump[] is sorted by (space, page),
 | 
						|
	so all pages from a given tablespace are consecutive. */
 | 
						|
	uint32_t	cur_space_id = dump[0].space();
 | 
						|
	fil_space_t*	space = fil_space_t::get(cur_space_id);
 | 
						|
	ulint		zip_size = space ? space->zip_size() : 0;
 | 
						|
 | 
						|
	PSI_stage_progress*	pfs_stage_progress __attribute__((unused))
 | 
						|
		= mysql_set_stage(srv_stage_buffer_pool_load.m_key);
 | 
						|
	mysql_stage_set_work_estimated(pfs_stage_progress, dump_n);
 | 
						|
	mysql_stage_set_work_completed(pfs_stage_progress, 0);
 | 
						|
 | 
						|
	for (i = 0; i < dump_n && !SHUTTING_DOWN(); i++) {
 | 
						|
 | 
						|
		/* space_id for this iteration of the loop */
 | 
						|
		const uint32_t this_space_id = dump[i].space();
 | 
						|
 | 
						|
		if (this_space_id >= SRV_SPACE_ID_UPPER_BOUND) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (this_space_id != cur_space_id) {
 | 
						|
			if (space) {
 | 
						|
				space->release();
 | 
						|
			}
 | 
						|
 | 
						|
			cur_space_id = this_space_id;
 | 
						|
			space = fil_space_t::get(cur_space_id);
 | 
						|
 | 
						|
			if (!space) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			zip_size = space->zip_size();
 | 
						|
		}
 | 
						|
 | 
						|
		/* JAN: TODO: As we use background page read below,
 | 
						|
		if tablespace is encrypted we cant use it. */
 | 
						|
		if (!space || dump[i].page_no() >= space->get_size() ||
 | 
						|
		    (space->crypt_data &&
 | 
						|
		     space->crypt_data->encryption != FIL_ENCRYPTION_OFF &&
 | 
						|
		     space->crypt_data->type != CRYPT_SCHEME_UNENCRYPTED)) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (space->is_stopping()) {
 | 
						|
			space->release();
 | 
						|
			space = nullptr;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		space->reacquire();
 | 
						|
		buf_read_page_background(space, dump[i], zip_size);
 | 
						|
 | 
						|
		if (buf_load_abort_flag) {
 | 
						|
			if (space) {
 | 
						|
				space->release();
 | 
						|
			}
 | 
						|
			buf_load_abort_flag = false;
 | 
						|
			ut_free(dump);
 | 
						|
			buf_load_status(
 | 
						|
				STATUS_INFO,
 | 
						|
				"Buffer pool(s) load aborted on request");
 | 
						|
			/* Premature end, set estimated = completed = i and
 | 
						|
			end the current stage event. */
 | 
						|
 | 
						|
			mysql_stage_set_work_estimated(pfs_stage_progress, i);
 | 
						|
			mysql_stage_set_work_completed(pfs_stage_progress, i);
 | 
						|
 | 
						|
			mysql_end_stage();
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
		if ((i+1) >= srv_buf_pool_load_pages_abort) {
 | 
						|
			buf_load_abort_flag = true;
 | 
						|
		}
 | 
						|
#endif
 | 
						|
	}
 | 
						|
 | 
						|
	if (space) {
 | 
						|
		space->release();
 | 
						|
	}
 | 
						|
 | 
						|
	ut_free(dump);
 | 
						|
 | 
						|
  os_aio_wait_until_no_pending_reads(true);
 | 
						|
 | 
						|
	ut_sprintf_timestamp(now);
 | 
						|
 | 
						|
	if (i == dump_n) {
 | 
						|
		buf_load_status(STATUS_INFO,
 | 
						|
			"Buffer pool(s) load completed at %s", now);
 | 
						|
		export_vars.innodb_buffer_pool_load_incomplete = 0;
 | 
						|
	} else if (!buf_load_abort_flag) {
 | 
						|
		buf_load_status(STATUS_INFO,
 | 
						|
			"Buffer pool(s) load aborted due to user instigated abort at %s",
 | 
						|
			now);
 | 
						|
		/* intentionally don't reset innodb_buffer_pool_load_incomplete
 | 
						|
                   as we don't want a shutdown to save the buffer pool */
 | 
						|
	} else {
 | 
						|
		buf_load_status(STATUS_INFO,
 | 
						|
			"Buffer pool(s) load aborted due to shutdown at %s",
 | 
						|
			now);
 | 
						|
		/* intentionally don't reset innodb_buffer_pool_load_incomplete
 | 
						|
                   as we want to abort without saving the buffer pool */
 | 
						|
	}
 | 
						|
 | 
						|
	/* Make sure that estimated = completed when we end. */
 | 
						|
	mysql_stage_set_work_completed(pfs_stage_progress, dump_n);
 | 
						|
	/* End the stage progress event. */
 | 
						|
	mysql_end_stage();
 | 
						|
}
 | 
						|
 | 
						|
/** Abort a currently running buffer pool load. */
 | 
						|
void buf_load_abort()
 | 
						|
{
 | 
						|
  buf_load_abort_flag= true;
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************//**
 | 
						|
This is the main task for buffer pool dump/load. when scheduled
 | 
						|
either performs a dump or load, depending on server state, state of the variables etc- */
 | 
						|
static void buf_dump_load_func(void *)
 | 
						|
{
 | 
						|
	ut_ad(!srv_read_only_mode);
 | 
						|
	static bool first_time = true;
 | 
						|
	if (first_time && srv_buffer_pool_load_at_startup) {
 | 
						|
 | 
						|
#ifdef WITH_WSREP
 | 
						|
		if (!get_wsrep_recovery()) {
 | 
						|
#endif /* WITH_WSREP */
 | 
						|
			srv_thread_pool->set_concurrency(srv_n_read_io_threads);
 | 
						|
			buf_load();
 | 
						|
			srv_thread_pool->set_concurrency();
 | 
						|
#ifdef WITH_WSREP
 | 
						|
		}
 | 
						|
#endif /* WITH_WSREP */
 | 
						|
	}
 | 
						|
	first_time = false;
 | 
						|
 | 
						|
	while (!SHUTTING_DOWN()) {
 | 
						|
		if (buf_dump_should_start) {
 | 
						|
			buf_dump_should_start = false;
 | 
						|
			buf_dump(true);
 | 
						|
		}
 | 
						|
		if (buf_load_should_start) {
 | 
						|
			buf_load_should_start = false;
 | 
						|
			buf_load();
 | 
						|
		}
 | 
						|
 | 
						|
		if (!buf_dump_should_start && !buf_load_should_start) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* In shutdown */
 | 
						|
	if (srv_buffer_pool_dump_at_shutdown && srv_fast_shutdown != 2) {
 | 
						|
		if (export_vars.innodb_buffer_pool_load_incomplete) {
 | 
						|
			buf_dump_status(STATUS_INFO,
 | 
						|
				"Dumping of buffer pool not started"
 | 
						|
				" as load was incomplete");
 | 
						|
#ifdef WITH_WSREP
 | 
						|
		} else if (get_wsrep_recovery()) {
 | 
						|
#endif /* WITH_WSREP */
 | 
						|
		} else {
 | 
						|
			buf_dump(false/* do complete dump at shutdown */);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/* Execute task with max.concurrency */
 | 
						|
static tpool::task_group tpool_group(1);
 | 
						|
static tpool::waitable_task buf_dump_load_task(buf_dump_load_func, &tpool_group);
 | 
						|
static bool load_dump_enabled;
 | 
						|
 | 
						|
/** Start async buffer pool load, if srv_buffer_pool_load_at_startup was set.*/
 | 
						|
void buf_load_at_startup()
 | 
						|
{
 | 
						|
  load_dump_enabled= true;
 | 
						|
  if (srv_buffer_pool_load_at_startup)
 | 
						|
    buf_do_load_dump();
 | 
						|
}
 | 
						|
 | 
						|
static void buf_do_load_dump()
 | 
						|
{
 | 
						|
  if (load_dump_enabled && !buf_dump_load_task.is_running())
 | 
						|
    srv_thread_pool->submit_task(&buf_dump_load_task);
 | 
						|
}
 | 
						|
 | 
						|
/** Wait for currently running load/dumps to finish*/
 | 
						|
void buf_load_dump_end()
 | 
						|
{
 | 
						|
  ut_ad(SHUTTING_DOWN());
 | 
						|
  buf_dump_load_task.wait();
 | 
						|
}
 |