diff --git a/debian/mariadb-server.install b/debian/mariadb-server.install index c125d17166110..d9920abcff737 100644 --- a/debian/mariadb-server.install +++ b/debian/mariadb-server.install @@ -59,6 +59,7 @@ usr/lib/mysql/plugin/simple_password_check.so usr/lib/mysql/plugin/sql_errlog.so usr/lib/mysql/plugin/type_mysql_json.so usr/lib/mysql/plugin/wsrep_info.so +usr/lib/mysql/plugin/mariadb_clone.so usr/share/doc/mariadb-server/mariadbd.sym.gz usr/share/man/man1/aria_chk.1 usr/share/man/man1/aria_dump_log.1 diff --git a/extra/mariabackup/aria_backup_client.cc b/extra/mariabackup/aria_backup_client.cc index 35b6cfbd1b7d0..24065cde56dd7 100644 --- a/extra/mariabackup/aria_backup_client.cc +++ b/extra/mariabackup/aria_backup_client.cc @@ -400,16 +400,18 @@ bool Table::copy(ds_ctxt_t *ds, bool is_index, unsigned thread_num) { for (ulonglong block= 0 ; ; block++) { size_t length = m_cap.block_size; if (is_index) { - if ((error= aria_read_index( - partition.m_index_file, &m_cap, block, copy_buffer) == - HA_ERR_END_OF_FILE)) - break; + error= aria_read_index(partition.m_index_file, + &m_cap, block, + copy_buffer); } else { - if ((error= aria_read_data( - partition.m_data_file, &m_cap, block, copy_buffer, &length) == - HA_ERR_END_OF_FILE)) - break; + error= aria_read_data(partition.m_data_file, + &m_cap, block, + copy_buffer, &length); } + + if (error == HA_ERR_END_OF_FILE) + break; + if (error) { msg(thread_num, "error: aria_read %s failed: %d", is_index ? "index" : "data", error); diff --git a/include/mysql/plugin.h b/include/mysql/plugin.h index ef70b36f497fd..78bd06df1b6f8 100644 --- a/include/mysql/plugin.h +++ b/include/mysql/plugin.h @@ -97,7 +97,7 @@ typedef struct st_mysql_xid MYSQL_XID; #define MYSQL_AUDIT_PLUGIN 5 #define MYSQL_REPLICATION_PLUGIN 6 #define MYSQL_AUTHENTICATION_PLUGIN 7 -#define MYSQL_MAX_PLUGIN_TYPE_NUM 12 /**< The number of plugin types */ +#define MYSQL_MAX_PLUGIN_TYPE_NUM 13 /**< The number of plugin types */ /* MariaDB plugin types */ /** Client and server password validation */ @@ -108,6 +108,8 @@ typedef struct st_mysql_xid MYSQL_XID; #define MariaDB_DATA_TYPE_PLUGIN 10 /**< Plugins for new native SQL functions */ #define MariaDB_FUNCTION_PLUGIN 11 +/** Plugin for cloning storage engine data */ +#define MariaDB_CLONE_PLUGIN 12 /* We use the following strings to define licenses for plugins */ #define PLUGIN_LICENSE_PROPRIETARY 0 diff --git a/include/mysql/plugin_audit.h.pp b/include/mysql/plugin_audit.h.pp index 2aeb19207de54..545dd0f6e3476 100644 --- a/include/mysql/plugin_audit.h.pp +++ b/include/mysql/plugin_audit.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/plugin_auth.h.pp b/include/mysql/plugin_auth.h.pp index 08e6b91ed9044..ab5d3e9318b90 100644 --- a/include/mysql/plugin_auth.h.pp +++ b/include/mysql/plugin_auth.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/plugin_clone.h b/include/mysql/plugin_clone.h new file mode 100644 index 0000000000000..c37708d81dcbb --- /dev/null +++ b/include/mysql/plugin_clone.h @@ -0,0 +1,89 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file mysql/plugin_clone.h +API for clone plugin +*/ + +#ifndef MYSQL_PLUGIN_CLONE_INCLUDED +#define MYSQL_PLUGIN_CLONE_INCLUDED + +#include "my_global.h" +#include "violite.h" +#include "plugin.h" + +/** Clone plugin interface version */ +#define MariaDB_CLONE_INTERFACE_VERSION 0x0100 + +/** + The descriptor structure for the plugin, that is referred from + st_mysql_plugin. +*/ + +struct Mysql_clone { + /** clone plugin interface version */ + int interface_version; + + /** Clone database from local server. + @param[in] thd server thread handle + @param[in] data_dir cloned data directory + @return error code, 0 on success */ + int (*clone_local)(THD *thd, const char *data_dir); + + /** Clone database from remote server. + @param[in] thd server thread handle + @param[in] remote_host remote host IP address + @param[in] remote_port remote server port + @param[in] remote_user remote user name + @param[in] remote_passwd remote user's password + @param[in] data_dir cloned data directory + @param[in] ssl_mode ssl mode for remote connection + @return error code, 0 on success */ + int (*clone_client)(THD *thd, const char *remote_host, uint remote_port, + const char *remote_user, const char *remote_passwd, + const char *data_dir, int ssl_mode); + + /** Clone database and send to remote clone client. + @param[in] thd server thread handle + @param[in] socket network socket to remote client + @return error code, 0 on success */ + int (*clone_server)(THD *thd, MYSQL_SOCKET socket); +}; + +/** Create clone handle to access the clone interfaces from server. +Called when Clone plugin is installed. +@param[in] plugin_name clone plugin name +@return error code */ +int clone_handle_create(const char *plugin_name); + +/** Drop clone handle. Called when Clone plugin is uninstalled. +@return error code */ +int clone_handle_drop(); + +/** Check if it is safe to uninstall clone plugin. +@param[in,out] plugin_info plugin +@return error code */ +int clone_handle_check_drop(MYSQL_PLUGIN plugin_info); + +#endif diff --git a/include/mysql/plugin_data_type.h.pp b/include/mysql/plugin_data_type.h.pp index 41b68c66fab42..75a3af2a5dd8e 100644 --- a/include/mysql/plugin_data_type.h.pp +++ b/include/mysql/plugin_data_type.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/plugin_encryption.h.pp b/include/mysql/plugin_encryption.h.pp index bbe46404fa15d..e6e94e2478172 100644 --- a/include/mysql/plugin_encryption.h.pp +++ b/include/mysql/plugin_encryption.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/plugin_ftparser.h.pp b/include/mysql/plugin_ftparser.h.pp index 06de1668e7658..187948788fd7c 100644 --- a/include/mysql/plugin_ftparser.h.pp +++ b/include/mysql/plugin_ftparser.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/plugin_function.h.pp b/include/mysql/plugin_function.h.pp index d56255818bfd2..52919e0e53fa9 100644 --- a/include/mysql/plugin_function.h.pp +++ b/include/mysql/plugin_function.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/plugin_password_validation.h.pp b/include/mysql/plugin_password_validation.h.pp index 529f50e26894c..451726d341c9c 100644 --- a/include/mysql/plugin_password_validation.h.pp +++ b/include/mysql/plugin_password_validation.h.pp @@ -22,6 +22,83 @@ void *dst, const char **end_ptr, int flags); } extern "C" { +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; +typedef struct mysql_clone_ssl_context_t { + int m_ssl_mode; + const char *m_ssl_key; + const char *m_ssl_cert; + const char *m_ssl_ca; + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; +extern struct clone_protocol_service_st { + THD* (*start_statement_fn)(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void (*finish_statement_fn)(THD* thd); + int (*get_charsets_fn)(THD* thd, void *char_sets); + int (*validate_charsets_fn)(THD* thd, void *char_sets); + int (*get_configs_fn)(THD* thd, void *configs); + int (*validate_configs_fn)(THD* thd, void *configs); + MYSQL* (*connect_fn)(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int (*send_command_fn)(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int (*get_response_fn)(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + void (*disconnect_fn)(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void (*get_error_fn)(THD* thd, uint32_t *err_num, + const char **err_mesg); + int (*get_command_fn)(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int (*send_response_fn)(THD* thd, bool secure, unsigned char *packet, + size_t length); + int (*send_error_fn)(THD* thd, unsigned char err_cmd, bool is_fatal); + int (*set_backup_stage_fn)(THD* thd, unsigned char stage); + int (*backup_lock_fn)(THD* thd, const char *db, const char *tbl); + int (*backup_unlock_fn)(THD* thd); +} *clone_protocol_service; + THD* clone_start_statement(THD* thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(THD* thd); + int clone_get_charsets(THD* thd, void *char_sets); + int clone_validate_charsets(THD* thd, void *char_sets); + int clone_get_configs(THD* thd, void *configs); + int clone_validate_configs(THD* thd, void *configs); + MYSQL* clone_connect(THD* thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + int clone_send_command(THD* thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + int clone_get_response(THD* thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + void clone_disconnect(THD* thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(THD* thd, uint32_t *err_num, + const char **err_mesg); + int clone_get_command(THD* thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + int clone_send_response(THD* thd, bool secure, unsigned char *packet, + size_t length); + int clone_send_error(THD* thd, unsigned char err_cmd, bool is_fatal); + int clone_set_backup_stage(THD* thd, unsigned char stage); + int clone_backup_lock(THD* thd, const char* db, const char* tbl); + int clone_backup_unlock(THD* thd); +} +extern "C" { extern void (*debug_sync_C_callback_ptr)(THD*, const char *, size_t); } extern "C" { diff --git a/include/mysql/service_clone_protocol.h b/include/mysql/service_clone_protocol.h new file mode 100644 index 0000000000000..f2d8482493357 --- /dev/null +++ b/include/mysql/service_clone_protocol.h @@ -0,0 +1,356 @@ +/* Copyright (c) 2018, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifndef CLONE_PROTOCOL_SERVICE +#define CLONE_PROTOCOL_SERVICE + +/** + @file + This service provides functions for clone plugin to + connect and interact with remote server's clone plugin + counterpart. + +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MYSQL_ABI_CHECK +#include +#include +#include +#endif /* MYSQL_ABI_CHECK */ + +typedef struct st_net_server NET_SERVER; +typedef struct st_mysql MYSQL; +typedef struct st_mysql_socket MYSQL_SOCKET; + +// #include "mysql_com_server.h" + +/** Connection parameters including SSL */ +typedef struct mysql_clone_ssl_context_t { + /** Clone ssl mode. Same as mysql client --ssl-mode */ + int m_ssl_mode; + /** Clone ssl private key. Same as mysql client --ssl-key */ + const char *m_ssl_key; + /** Clone ssl certificate. Same as mysql client --ssl-cert */ + const char *m_ssl_cert; + /** Clone ssl certificate authority. Same as mysql client --ssl-ca */ + const char *m_ssl_ca; + + /** Enable network compression. */ + bool m_enable_compression; + NET_SERVER *m_server_extn; +} mysql_clone_ssl_context; + +extern struct clone_protocol_service_st { +/** + Start and set session and statement key form current thread + @param[in,out] thd server session THD + @param[in] thread_key PSI key for thread + @param[in] statement_key PSI Key for statement + @param[in] thd_name thread name based on PSI key +*/ + MYSQL_THD (*start_statement_fn)(MYSQL_THD thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); +/** + Finish statement and session + @param[in,out] thd server session THD +*/ + void (*finish_statement_fn)(MYSQL_THD thd); + +/** + Get all character set and collations + @param[in,out] thd server session THD + @param[out] char_sets all character set collations + @return error code. +*/ + int (*get_charsets_fn)(MYSQL_THD thd, void *char_sets); + +/** + Check if all characters sets are supported by server + @param[in,out] thd server session THD + @param[in] char_sets all character set collations to validate + @return error code. +*/ + int (*validate_charsets_fn)(MYSQL_THD thd, void *char_sets); + +/** + Get system configuration parameter values. + @param[in,out] thd server session THD + @param[in,out] configs a list of configuration key value pair + keys are input and values are output + @return error code. +*/ + int (*get_configs_fn)(MYSQL_THD thd, void *configs); + +/** + Check if configuration parameter values match + @param[in,out] thd server session THD + @param[in] configs a list of configuration key value pair + @return error code. +*/ + int (*validate_configs_fn)(MYSQL_THD thd, void *configs); + +/** + Connect to a remote server and switch to clone protocol + @param[in,out] thd server session THD + @param[in] host host name to connect to + @param[in] port port number to connect to + @param[in] user user name on remote host + @param[in] passwd password for the user + @param[in] ssl_ctx client ssl context + @param[out] socket Network socket for the connection + + @return Connection object if successful. +*/ + MYSQL* (*connect_fn)(MYSQL_THD thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); +/** + Execute clone command on remote server + @param[in,out] thd local session THD + @param[in,out] connection connection object + @param[in] set_active set socket active for current THD + @param[in] command remote command + @param[in] com_buffer data following command + @param[in] buffer_length data length + @return error code. +*/ + int (*send_command_fn)(MYSQL_THD thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + +/** + Get response from remote server + @param[in,out] thd local session THD + @param[in,out] connection connection object + @param[in] set_active set socket active for current THD + @param[in] timeout timeout in seconds + @param[out] packet response packet + @param[out] length packet length + @param[out] net_length network data length for compressed data + @return error code. +*/ + int (*get_response_fn)(MYSQL_THD thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + +/** + Kill a remote connection + @param[in,out] connection connection object + @param[in] kill_connection connection to kill + @return error code. +*/ + int (*kill_fn)(MYSQL *connection, MYSQL *kill_connection); + +/** + Disconnect from a remote server + @param[in,out] thd local session THD + @param[in,out] connection connection object + @param[in] is_fatal if closing after fatal error + @param[in] clear_error clear any earlier error in session +*/ + void (*disconnect_fn)(MYSQL_THD thd, MYSQL *connection, bool is_fatal, + bool clear_error); +/** + Get error number and message. + @param[in,out] thd local session THD + @param[out] err_num error number + @param[out] err_mesg error message text +*/ + void (*get_error_fn)(MYSQL_THD thd, uint32_t *err_num, + const char **err_mesg); + +/** + Get command from client + @param[in,out] thd server session THD + @param[out] command remote command + @param[out] com_buffer data following command + @param[out] buffer_length data length + @return error code. +*/ + int (*get_command_fn)(MYSQL_THD thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + +/** + Send response to client. + @param[in,out] thd server session THD + @param[in] secure needs to be sent over secure connection + @param[in] packet response packet + @param[in] length packet length + @return error code. +*/ + int (*send_response_fn)(MYSQL_THD thd, bool secure, unsigned char *packet, + size_t length); + +/** + Send error to client + @param[in,out] thd server session THD + @param[in] err_cmd error response command + @param[in] is_fatal if fatal error + @return error code. +*/ + int (*send_error_fn)(MYSQL_THD thd, unsigned char err_cmd, bool is_fatal); + +/** + Set server to desired backup stage + @param[in,out] thd server session THD + @param[in] stage backup stage + @return error code. +*/ + int (*set_backup_stage_fn)(MYSQL_THD thd, unsigned char stage); +/** + Set backup lock on the given table + @param thd server session thread + @param db database name + @param tbl table name + @return error code. +*/ + int (*backup_lock_fn)(MYSQL_THD thd, const char *db, const char *tbl); +/** + Unlock the backup lock on the table + @param thd server session thread + @return error code +*/ + int (*backup_unlock_fn)(MYSQL_THD thd); + +} *clone_protocol_service; + +#ifdef MYSQL_DYNAMIC_PLUGIN +#define clone_start_statement(thd, thd_key, stmt_key, thd_name) \ + (clone_protocol_service->start_statement_fn((thd), (thd_key), (stmt_key), (thd_name))) + +#define clone_finish_statement(thd) \ + (clone_protocol_service->finish_statement_fn(thd)) + + #define clone_get_charsets(thd, char_sets) \ + (clone_protocol_service->get_charsets_fn((thd), (char_sets))) + + #define clone_validate_charsets(thd, char_sets) \ + (clone_protocol_service->validate_charsets_fn((thd), (char_sets))) + +#define clone_get_configs(thd, configs) \ + (clone_protocol_service->get_configs_fn((thd), (configs))) + + #define clone_validate_configs(thd, configs) \ + (clone_protocol_service->validate_configs_fn((thd), (configs))) + +#define clone_connect(thd, host, port, user, passwd, ssl_ctx, socket) \ + (clone_protocol_service->connect_fn((thd), (host), (port), (user), (passwd), \ + (ssl_ctx), (socket))) + +#define clone_send_command(thd, connection, set_active, command, com_buffer, \ + buffer_length) \ + (clone_protocol_service->send_command_fn((thd), (connection), \ + (set_active), (command), (com_buffer), (buffer_length))) + +#define clone_get_response(thd, connection, set_active, timeout, packet, length, \ + net_length) \ + (clone_protocol_service->get_response_fn((thd), (connection), \ + (set_active), (timeout), (packet), (length), (net_length))) + +#define clone_kill(connection, kill_connection) \ + (clone_protocol_service->kill_fn((connection), (kill_connection))) + +#define clone_disconnect(thd, connection, is_fatal, clear_error) \ + (clone_protocol_service->disconnect_fn((thd), (connection), (is_fatal), \ + (clear_error))) + +#define clone_get_error(thd, err_num, err_mesg) \ + (clone_protocol_service->get_error_fn((thd), (err_num), (err_mesg))) + +#define clone_get_command(thd, command, com_buffer, buffer_length) \ + (clone_protocol_service->get_command_fn((thd), (command), (com_buffer), \ + (buffer_length))) + +#define clone_send_response(thd, secure, packet, length) \ + (clone_protocol_service->send_response_fn((thd), (secure), (packet), (length))) + +#define clone_send_error(thd, err_cmd, is_fatal) \ + (clone_protocol_service->send_error_fn((thd), (err_cmd), (is_fatal))) + +#define clone_set_backup_stage(thd, stage) \ + (clone_protocol_service->set_backup_stage_fn((thd), (stage))) + +#define clone_backup_lock(thd, db, tbl) \ + (clone_protocol_service->backup_lock_fn((thd), (db), (tbl))) + +#define clone_backup_unlock(thd) \ + (clone_protocol_service->backup_unlock_fn((thd))) +#else + MYSQL_THD clone_start_statement(MYSQL_THD thd, unsigned int thread_key, + unsigned int statement_key, + const char* thd_name); + void clone_finish_statement(MYSQL_THD thd); + + int clone_get_charsets(MYSQL_THD thd, void *char_sets); + + int clone_validate_charsets(MYSQL_THD thd, void *char_sets); + + int clone_get_configs(MYSQL_THD thd, void *configs); + + int clone_validate_configs(MYSQL_THD thd, void *configs); + + MYSQL* clone_connect(MYSQL_THD thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, + MYSQL_SOCKET *socket); + + int clone_send_command(MYSQL_THD thd, MYSQL *connection, bool set_active, + unsigned char command, unsigned char *com_buffer, + size_t buffer_length); + + int clone_get_response(MYSQL_THD thd, MYSQL *connection, bool set_active, + uint32_t timeout, unsigned char **packet, + size_t *length, size_t *net_length); + + int clone_kill(MYSQL *connection, MYSQL *kill_connection); + + void clone_disconnect(MYSQL_THD thd, MYSQL *connection, bool is_fatal, + bool clear_error); + void clone_get_error(MYSQL_THD thd, uint32_t *err_num, + const char **err_mesg); + + int clone_get_command(MYSQL_THD thd, unsigned char *command, + unsigned char **com_buffer, size_t *buffer_length); + + int clone_send_response(MYSQL_THD thd, bool secure, unsigned char *packet, + size_t length); + + int clone_send_error(MYSQL_THD thd, unsigned char err_cmd, bool is_fatal); + + int clone_set_backup_stage(MYSQL_THD thd, unsigned char stage); + + int clone_backup_lock(MYSQL_THD thd, const char *db, const char *tbl); + int clone_backup_unlock(MYSQL_THD thd); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CLONE_PROTOCOL_SERVICE */ diff --git a/include/mysql/services.h b/include/mysql/services.h index 94f7bb3b2da14..a89ec770884ea 100644 --- a/include/mysql/services.h +++ b/include/mysql/services.h @@ -20,6 +20,7 @@ extern "C" { #endif #include +#include #include #include #include diff --git a/include/mysql_com.h b/include/mysql_com.h index b9fae54e7f476..a05ff014aca85 100644 --- a/include/mysql_com.h +++ b/include/mysql_com.h @@ -95,7 +95,7 @@ enum enum_server_command COM_STMT_PREPARE, COM_STMT_EXECUTE, COM_STMT_SEND_LONG_DATA, COM_STMT_CLOSE, COM_STMT_RESET, COM_SET_OPTION, COM_STMT_FETCH, COM_DAEMON, COM_UNIMPLEMENTED, /* COM_BINLOG_DUMP_GTID in MySQL */ - COM_RESET_CONNECTION, + COM_RESET_CONNECTION, COM_CLONE, /* don't forget to update const char *command_name[] in sql_parse.cc */ COM_MDB_GAP_BEG, COM_MDB_GAP_END=249, diff --git a/include/service_versions.h b/include/service_versions.h index 45cad4d86ae06..b9999f0217295 100644 --- a/include/service_versions.h +++ b/include/service_versions.h @@ -24,6 +24,7 @@ #define VERSION_kill_statement 0x1000 #define VERSION_base64 0x0100 +#define VERSION_clone_protocol 0x0100 #define VERSION_encryption 0x0300 #define VERSION_encryption_scheme 0x0100 #define VERSION_logger 0x0300 diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 58e5dbcb0d393..73cd86b13c6f9 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -164,7 +164,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/scan_char.h ../sql/opt_hints.cc ../sql/opt_hints.h ../sql/opt_trace_ddl_info.cc ../sql/opt_trace_ddl_info.h - ${GEN_SOURCES} + ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/libservices/CMakeLists.txt b/libservices/CMakeLists.txt index 37c24646791f7..b08c138e3e38c 100644 --- a/libservices/CMakeLists.txt +++ b/libservices/CMakeLists.txt @@ -17,6 +17,7 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) SET(MYSQLSERVICES_SOURCES base64_service.c + clone_protocol_service.c debug_sync_service.c encryption_scheme_service.c encryption_service.c diff --git a/libservices/clone_protocol_service.c b/libservices/clone_protocol_service.c new file mode 100644 index 0000000000000..f73c2a97d700d --- /dev/null +++ b/libservices/clone_protocol_service.c @@ -0,0 +1,17 @@ +/* Copyright (C) 2024 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 St, Fifth Floor, Boston, MA 02110-1335 USA */ + +#include +SERVICE_VERSION clone_protocol_service= (void *) VERSION_clone_protocol; diff --git a/mysql-test/collections/buildbot_suites.bat b/mysql-test/collections/buildbot_suites.bat index 61c1a5c09a9a2..58c0eab6efd84 100644 --- a/mysql-test/collections/buildbot_suites.bat +++ b/mysql-test/collections/buildbot_suites.bat @@ -11,4 +11,5 @@ auth_gssapi,^ mysql_sha2,^ query_response_time,^ rocksdb,^ +clone,^ sysschema diff --git a/mysql-test/main/mysqld--help.result b/mysql-test/main/mysqld--help.result index 9560ce074f7fa..ec41493b25023 100644 --- a/mysql-test/main/mysqld--help.result +++ b/mysql-test/main/mysqld--help.result @@ -1954,8 +1954,8 @@ performance-schema-max-rwlock-instances -1 performance-schema-max-socket-classes 10 performance-schema-max-socket-instances -1 performance-schema-max-sql-text-length 1024 -performance-schema-max-stage-classes 160 -performance-schema-max-statement-classes 227 +performance-schema-max-stage-classes 175 +performance-schema-max-statement-classes 229 performance-schema-max-statement-stack 10 performance-schema-max-table-handles -1 performance-schema-max-table-instances -1 diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl index 03128ce897e5a..348ae53786048 100755 --- a/mysql-test/mariadb-test-run.pl +++ b/mysql-test/mariadb-test-run.pl @@ -219,6 +219,7 @@ END versioning- period- sysschema- + clone- ); my $opt_suites; diff --git a/mysql-test/suite/clone/include/clone_command.inc b/mysql-test/suite/clone/include/clone_command.inc new file mode 100644 index 0000000000000..19470547c6089 --- /dev/null +++ b/mysql-test/suite/clone/include/clone_command.inc @@ -0,0 +1,186 @@ +## Clone command test + +# These variables can to be set before sourcing this file. Currently we used +# to test both local and remote clone. +# +# 1. Clone command is expected to return error +# --let clone_err= +# +# 2. Test Remote Clone command. Default is local clone. +# --let remote_clone = 1 +# +# 3. Remote Clone command is expected to return error and the error number +# is different from local clone. +# --let clone_remote_err = +# +# 4. Skip clone_valid_donor_list configuration for testing error cases +# --let skip_donor_config = 1 +# +# 5. Test clone automatic tuning of threads +# --let clone_auto_tune = 1 +# +# 6. Test clone command forcing SSL [REQUIRES SSL] +# --let clone_require_ssl = 1 +# +# 7. Test clone command forcing insecure connection [REQUIRES NO SSL] +# --let clone_require_no_ssl = 1 +# +# 8. Test clone command forcing SSL certificate validation +# --let clone_require_ssl_certificate = 1 +# +--let $remote_dir_clause = DATA DIRECTORY = '$CLONE_DATADIR' + +if ($clone_remote_replace) { + --let $remote_dir_clause = + --let $remote_clone = 1 +} + +if ($clone_err != ER_PLUGIN_IS_NOT_LOADED) { + if ($clone_throttle) { + SET GLOBAL clone_max_data_bandwidth = 5; + } + + if($remote_clone) { + if(!$skip_donor_config) { + --replace_result $HOST HOST $PORT PORT + --eval SET GLOBAL clone_valid_donor_list = '$HOST:$PORT' + } + } +} + +if($remote_clone) { + + # Increase network timeout for valgrind test + if ($VALGRIND_TEST) { + --disable_query_log + SET GLOBAL net_read_timeout = 300; + SET LOCAL net_read_timeout = 300; + SET GLOBAL net_write_timeout = 300; + SET LOCAL net_write_timeout = 300; + --enable_query_log + } + + if ($delay_after_data_drop) { + # Delay the clone process for 10 seconds + SET GLOBAL clone_delay_after_data_drop = 2; + } + + # Execute Remote Clone command with error + if ($clone_remote_err) { + + --replace_result $CLONE_DATADIR CLONE_DATADIR $HOST HOST $PORT PORT $USER USER + if ($clone_require_no_ssl) { + --error $clone_remote_err + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause REQUIRE NO SSL + } + + if(!$clone_require_no_ssl) { + --error $clone_remote_err + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause + } + } + + if (!$clone_remote_err) { + if ($clone_err) { + + if ($clone_err == ER_FILE_EXISTS_ERROR) { + + --replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR $HOST HOST $PORT PORT $USER USER + --error $clone_err + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause + } + + if ($clone_err != ER_FILE_EXISTS_ERROR) { + + --replace_regex /\([0-9]+\)\./(socket errno)./ + --replace_result $CLONE_DATADIR CLONE_DATADIR $HOST HOST $PORT PORT $USER USER + --error $clone_err + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause + } + } + + # Execute Remote Clone command + if (!$clone_err) { + + # Execute Remote Clone command + if($clone_require_ssl) { + if($clone_require_ssl_certificate) { + SHOW VARIABLES LIKE "%clone_ssl%"; + --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR + --eval SET GLOBAL clone_ssl_ca = '$MYSQL_TEST_DIR/std_data/cacert.pem' + --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR + --eval SET GLOBAL clone_ssl_cert = '$MYSQL_TEST_DIR/std_data/client-cert.pem' + --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR + --eval SET GLOBAL clone_ssl_key = '$MYSQL_TEST_DIR/std_data/client-key.pem' + } + --replace_result $CLONE_DATADIR CLONE_DATADIR $HOST HOST $PORT PORT $USER USER + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause REQUIRE SSL + } + + --replace_result $CLONE_DATADIR CLONE_DATADIR $HOST HOST $PORT PORT $USER USER + if(!$clone_require_ssl) { + if ($clone_require_no_ssl) { + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause REQUIRE NO SSL + } + + if(!$clone_require_no_ssl) { + --eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' $remote_dir_clause + } + } + } + } + + # For remote replace, wait for server to restart, unless an error was expected + if ($clone_remote_replace) { + if (!$clone_err) { + --source include/wait_until_disconnected.inc + --source include/wait_until_connected_again.inc + if (!$clone_recovery_error) { + --disable_query_log + use test; + --enable_query_log + } + } + } + + if ($VALGRIND_TEST) { + --disable_query_log + SET GLOBAL net_read_timeout = default; + SET LOCAL net_read_timeout = default; + SET GLOBAL net_write_timeout = default; + SET LOCAL net_write_timeout = default; + --enable_query_log + } + + if ($delay_after_data_drop) { + SET GLOBAL clone_delay_after_data_drop = 0; + } +} + +if (!$remote_clone) { + + # Execute Local Clone command with error + if ($clone_err) { + + if ($clone_err == ER_FILE_EXISTS_ERROR) { + + --replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR + --error $clone_err + --eval CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' + } + + if ($clone_err != ER_FILE_EXISTS_ERROR) { + + --replace_result $CLONE_DATADIR CLONE_DATADIR + --error $clone_err + --eval CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' + } + } + + # Execute Local Clone command + if (!$clone_err) { + + --replace_result $CLONE_DATADIR CLONE_DATADIR + --eval CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' + } +} diff --git a/mysql-test/suite/clone/include/clone_command_send.inc b/mysql-test/suite/clone/include/clone_command_send.inc new file mode 100644 index 0000000000000..27922619612d7 --- /dev/null +++ b/mysql-test/suite/clone/include/clone_command_send.inc @@ -0,0 +1,35 @@ +## Send Clone command + +# These variables have to be set before sourcing this file. Currently we used +# to test both local and remote clone. +# +# 1. Clone command is expected to return error +# --let clone_err= +# +# 2. Test Remote Clone command. Default is local clone. +# --let remote_clone = 1 +# +# 3. Test clone automatic tuning of threads +# --let clone_auto_tune = 1 +# + +if ($clone_err != ER_PLUGIN_IS_NOT_LOADED) { + if($remote_clone) { + if(!$skip_donor_config) { + --replace_result $HOST HOST $PORT PORT + --eval SET GLOBAL clone_valid_donor_list = '$HOST:$PORT' + } + } +} + +if($remote_clone) { + # Execute Remote Clone command + --replace_result $CLONE_DATADIR CLONE_DATADIR $HOST HOST $PORT PORT $USER USER + --send_eval CLONE INSTANCE FROM $USER@$HOST:$PORT IDENTIFIED BY '' DATA DIRECTORY = '$CLONE_DATADIR' +} + +if (!$remote_clone) { + # Execute Local Clone command + --replace_result $CLONE_DATADIR CLONE_DATADIR + --send_eval CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' +} diff --git a/mysql-test/suite/clone/include/clone_connection_begin.inc b/mysql-test/suite/clone/include/clone_connection_begin.inc new file mode 100644 index 0000000000000..857abdf8f5c7f --- /dev/null +++ b/mysql-test/suite/clone/include/clone_connection_begin.inc @@ -0,0 +1,133 @@ +## Create connection to clone instance optionally restarting instance with +## monitoring process. + +# These variables can to be set before sourcing this file. Currently we used +# to test both with and without monitoring process. +# +# 1. Need to restart mysqld with monitoring process +# --let inst_monitor = 1 +# +# 2. Mysqld server instance number for clone +# --let clone_inst_number = 1/2/3 ... +# - Instances must be configured in .cnf +# - SERVER_PORT_[n] ENV must be set to server PORT in .cnf +# +# 3. Mysqld user name for connecting to the instance. +# --let clone_user = +# +# 4. Number of connections to be created to the instance +# --let clone_connections = 1/2/3 ... +# Connections have the name as clone_conn_ +# e.g. clone_conn_1, clone_conn_2, clone_conn_3 etc. +# +# This script should be used in pair with clone_connection_end.inc +# --source clone_connection_begin.inc +# ... +# --source clone_connection_end.inc + +--disable_query_log +if (!$clone_user) { + --let $clone_user = 'root' +} + +if (!$clone_connections) { + --let $clone_connections = 1 +} + +if ($clone_inst_number) { + --let $clone_port= \$SERVER_PORT_$clone_inst_number + --connect (clone_conn_1, 127.0.0.1, $clone_user,,test,$clone_port) +} + +if (!$clone_inst_number) { + --connection default +} + +# Get Server ID +--let $SERVER_ID= `SELECT @@server_id` + +if ($clone_inst_number) { + if ($SERVER_ID == 1) { + --disconnect clone_conn_1 + --connection default + } +} + +if ($SERVER_ID != 1) { + --echo Install clone plugin on recipient server + --replace_result $MARIADB_CLONE_SO CLONE_PLUGIN + --eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' +} + +# A. Check if we need monitoring process. Taken from mysqld_safe.test +if ($inst_monitor) { + + # 1. Set variables to be used in parameters of mysqld_safe. + --let $SERVER_ID= `SELECT @@server_id` + --let $MYSQLD_DATADIR= `SELECT @@datadir` + --let $MYSQL_BASEDIR= `SELECT @@basedir` + --let $MYSQL_MESSAGESDIR= `SELECT @@lc_messages_dir` + + --let $MYSQL_PIDFILE= `SELECT @@pid_file` + --let $MYSQL_SOCKET= `SELECT @@socket` + --let $MYSQLX_SOCKET= `SELECT @@mysqlx_socket` + --let $MYSQL_PORT= `SELECT @@port` + --let $MYSQLX_PORT= `SELECT @@mysqlx_port` + --let $MYSQL_PLUGIN_DIR = `SELECT @@plugin_dir` + + --let $page_size= `select @@innodb_page_size` + --let $error_verbosity = `select @@log_error_verbosity` + + # mysqld_path to be passed to --ledir + perl; + my $dir = $ENV{'MYSQLTEST_VARDIR'}; + open ( OUTPUT, ">$dir/tmp/mysqld_path_file.inc") ; + my $path = $ENV{MYSQLD}; + $path =~ /^(.*)\/([^\/]*)$/; + print OUTPUT "let \$mysqld_path = $1;\n"; + print OUTPUT "let \$mysqld_bin = $2;\n"; + close (OUTPUT); + EOF + + # Get the value of the variable to MTR, from perl + --source $MYSQLTEST_VARDIR/tmp/mysqld_path_file.inc + + # Remove the temp file + --remove_file $MYSQLTEST_VARDIR/tmp/mysqld_path_file.inc + + # 2. Shutdown mysqld instance which is started by mtr. + --let $_expect_file_name= $MYSQLTEST_VARDIR/tmp/mysqld.$SERVER_ID.expect + --exec echo "wait" > $_expect_file_name + --shutdown_server + --source include/wait_until_disconnected.inc + + # 3. Run mysqld_safe script + --exec sh $MYSQLD_SAFE --defaults-file=$MYSQLTEST_VARDIR/my.cnf --server-id=$SERVER_ID --log-error=$MYSQLTEST_VARDIR/log/mysqld.$SERVER_ID.err --log-error-verbosity=$error_verbosity --basedir=$MYSQL_BASEDIR --ledir=$mysqld_path --mysqld=$mysqld_bin --datadir=$MYSQLD_DATADIR --socket=$MYSQL_SOCKET --mysqlx_socket=$MYSQLX_SOCKET --pid-file=$MYSQL_PIDFILE --port=$MYSQL_PORT --mysqlx_port=$MYSQLX_PORT --plugin_dir=$MYSQL_PLUGIN_DIR --timezone=SYSTEM --log-output=file --secure-file-priv="" --core-file --lc-messages-dir=$MYSQL_MESSAGESDIR --innodb-page-size=$page_size < /dev/null > /dev/null 2>&1 & + --source include/wait_until_connected_again.inc +} + +# B. Create the connections +--let $conn_nummber = 1 + +# For non default server, we have already created the first connection +if ($SERVER_ID != 1) { + --let $conn_nummber = 2 +} + +while ($conn_nummber <= $clone_connections) +{ + --let $conn_name = clone_conn_$conn_nummber + + if ($clone_inst_number) { + --let $clone_port= \$SERVER_PORT_$clone_inst_number + --connect ($conn_name, 127.0.0.1, $clone_user,,test,$clone_port) + } + + # Connect to the default instance if instance number is not provided. + if (!$clone_inst_number) { + --connect ($conn_name, localhost, $clone_user,,test) + } + --inc $conn_nummber +} +--connection default +--enable_query_log diff --git a/mysql-test/suite/clone/include/clone_connection_end.inc b/mysql-test/suite/clone/include/clone_connection_end.inc new file mode 100644 index 0000000000000..4fd77933ef42a --- /dev/null +++ b/mysql-test/suite/clone/include/clone_connection_end.inc @@ -0,0 +1,82 @@ +## Create connection to clone instance optionally restarting instance with +## monitoring process. + +# These variables can to be set before sourcing this file. Currently we used +# to test both with and without monitoring process. +# +# 1. Need to restart mysqld with monitoring process +# --let inst_monitor = 1 +# +# 2. Number of connections to be dropped from the instance +# --let clone_connections = 1/2/3 ... +# Connections have the name as clone_conn_ +# e.g. clone_conn_1, clone_conn_2, clone_conn_3 etc. +# +# 3. This script should be used in pair with clone_connection_begin.inc +# --source clone_connection_begin.inc +# ... +# --source clone_connection_end.inc +--disable_query_log +if (!$clone_user) { + --let $clone_user = 'root' +} + +if (!$clone_connections) { + --let $clone_connections = 1 +} + +# A. Drop the connections +--connection clone_conn_1 +# In case server is restarted, we need to reconnect +--enable_reconnect +--source include/wait_until_connected_again.inc + +# Get Server ID +--let $SERVER_ID= `SELECT @@server_id` + +--let $conn_nummber = 1 + +# If not the default instance, keep first connection. +if ($SERVER_ID != 1) { + --let $conn_nummber =2 + + --echo Uninstall clone plugin on recipient server + UNINSTALL PLUGIN clone; +} + +while ($conn_nummber <= $clone_connections) +{ + --let $conn_name = clone_conn_$conn_nummber + --disconnect $conn_name + --inc $conn_nummber +} + +# B. Check if we need monitoring process. Taken from mysqld_safe.test +if ($inst_monitor) { + + # Reconnect default if we shut down default server. + if ($SERVER_ID == 1) { + --connection default + } + + # 1. Shutdown the server + --disable_query_log + --enable_query_log + --exec echo "wait" > $MYSQLTEST_VARDIR/tmp/mysqld.$SERVER_ID.expect + --shutdown_server + --source include/wait_until_disconnected.inc + + # 2. Restart mysqld having $SERVER_ID + --exec echo "restart" > $MYSQLTEST_VARDIR/tmp/mysqld.$SERVER_ID.expect + --source include/wait_until_disconnected.inc + --source include/wait_until_connected_again.inc +} + +# Now disconnect the skipped connection +if ($SERVER_ID != 1) { + --disconnect clone_conn_1 +} + +# Switch to default connection +--connection default +--enable_query_log diff --git a/mysql-test/suite/clone/include/create_schema.inc b/mysql-test/suite/clone/include/create_schema.inc new file mode 100644 index 0000000000000..c29adc9ead558 --- /dev/null +++ b/mysql-test/suite/clone/include/create_schema.inc @@ -0,0 +1,115 @@ +## Create test schema +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; + +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; + +DELIMITER |; + +# Procedure to execute dml commands in loop +# p_dml_type [IN] DML type +# 0 -> INSERT +# 1 -> UPDATE +# 2 -> DELETE +# p_key_min [IN] Minimum key value +# p_key_range [IN] Maximum key range +# p_loop_count [IN] Number of times to execute +# p_frequency [IN] Frequency of commit/rollback +# p_is_rand [IN] chose key randomly and do rollback and commit + +CREATE PROCEDURE execute_dml( + p_dml_type INT, + p_key_min INT, + p_key_range INT, + p_loop_count INT, + p_frequency INT, + p_is_rand INT) +BEGIN + DECLARE v_idx INT DEFAULT 0; + DECLARE v_commit INT DEFAULT 0; + DECLARE v_key INT DEFAULT 0; + + /* Loop and INSERT data at random position */ + WHILE(v_idx < p_loop_count) DO + + /* Generate key between 1 to p_loop_count */ + IF p_is_rand = 1 THEN + SET v_key = p_key_min + FLOOR(RAND() * p_key_range); + ELSE + SET v_key = p_key_min + (v_idx % p_key_range); + END IF; + + CASE p_dml_type + + WHEN 0 THEN + SET @clol3_text = CONCAT('Clone Test Row - ', v_key); + INSERT INTO t1 (col1, col2, col3, col4) VALUES ( + v_key, v_key * 10, + @clol3_text, REPEAT('Large Column Data ', 2048)) + ON DUPLICATE KEY UPDATE col2 = col2 + 1; + + INSERT INTO t2 (col1, col2, col3, col4) VALUES ( + v_key, v_key * 10, + @clol3_text, REPEAT('Large Column Data ', 2048)) + ON DUPLICATE KEY UPDATE col2 = col2 + 1; + + WHEN 1 THEN + UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; + UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; + + WHEN 2 THEN + DELETE FROM t1 WHERE col1 = v_key; + DELETE FROM t2 WHERE col1 = v_key; + + ELSE + DELETE FROM t1; + DELETE FROM t2; + END CASE; + + SET v_idx = v_idx + 1; + + /* Commit or rollback work at specified frequency. */ + IF v_idx % p_frequency = 0 THEN + + SET v_commit = FLOOR(RAND() * 2); + IF v_commit = 0 AND p_is_rand = 1 THEN + ROLLBACK; + START TRANSACTION; + ELSE + COMMIT; + START TRANSACTION; + END IF; + END IF; + + END WHILE; + COMMIT; +END| + +if ($clone_ddl) { + +# Procedure to execute ddl rebuild in loop +# p_loop_count [IN] Number of times to execute + +CREATE PROCEDURE execute_ddl( + p_loop_count INT) +BEGIN + DECLARE v_idx INT DEFAULT 0; + + /* Loop and execute DDL. */ + /* Concurrent DDL and DML creates MDL deadlock. Disabled till fixed. */ + WHILE(v_idx < p_loop_count) DO + + ALTER TABLE t1 ENGINE = InnoDB; + + ALTER TABLE t2 ENGINE = InnoDB; + + DO SLEEP(0.1); + + SET v_idx = v_idx + 1; + + END WHILE; +END| + +} + +DELIMITER ;| diff --git a/mysql-test/suite/clone/include/create_schema.opt b/mysql-test/suite/clone/include/create_schema.opt new file mode 100644 index 0000000000000..423cae6133bea --- /dev/null +++ b/mysql-test/suite/clone/include/create_schema.opt @@ -0,0 +1 @@ +--skip_partition=0 diff --git a/mysql-test/suite/clone/include/ddl_common.inc b/mysql-test/suite/clone/include/ddl_common.inc new file mode 100644 index 0000000000000..6d85712fc28d4 --- /dev/null +++ b/mysql-test/suite/clone/include/ddl_common.inc @@ -0,0 +1,312 @@ +## Common file for controlled DDL execution with concurrent Clone +## +## 1. ddl_op : DDL operation - CREATE TABLE, DROP TABLE, ALTER TABLE ... +## 2. ddl_stmt : DDL statement text following table name +## 3. ddl_post : Any other follow up clause like partition +## 4. ddl_table_extn : Second table name extension - new [t1_new, t2_new ...] +## 5. dml_stmt1 : DML to be executed after DDL during stage-1 FILE COPY +## 6. dml_stmt11 : DML to be executed after DDL before sending DDL metadata in FILE COPY +## 7. dml_stmt2 : DML to be executed after DDL during stage-1 PAGE COPY +## 8. dml_stmt21 : DML to be executed after DDL before sending DDL metadata in PAGE COPY +## 9. ddl_skip_check : Skip checking data. Used tables are dropped +##10. ddl_encryption : restart server with keyring plugin +--source include/big_test.inc +--source include/not_parallel.inc +--source include/have_component_keyring_file.inc + +--source include/count_sessions.inc + +## Get rid of the binary logs from previous run. +RESET BINARY LOGS AND GTIDS; + +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR= `select @@datadir;` + +if ($remote_clone) { + SET GLOBAL DEBUG = '+d,remote_release_clone_file_pin'; +} + +if (!$remote_clone) { + SET GLOBAL DEBUG = '+d,local_release_clone_file_pin'; +} + +if ($ddl_encryption) { + SET GLOBAL DEBUG = '+d,log_redo_with_invalid_master_key'; +} + +if ($ddl_redo_encrypt) { + --let $skip_ddl = 1 +} + +--let $ddl_exec = 1 +--let $table_num = 0 + +if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc +} + +if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = +} + +SET DEBUG_SYNC = 'clone_before_init_meta SIGNAL start_ddl_file_init WAIT_FOR resume_file_init'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_ddl_file WAIT_FOR resume_file'; +SET DEBUG_SYNC = 'clone_file_copy_end_before_ack SIGNAL start_ddl_file_ack WAIT_FOR resume_file_ack'; +SET DEBUG_SYNC = 'clone_before_file_ddl_meta SIGNAL start_ddl_file_meta WAIT_FOR resume_file_meta'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_ddl_page WAIT_FOR resume_page'; +SET DEBUG_SYNC = 'clone_before_page_ddl_meta SIGNAL start_ddl_page_meta WAIT_FOR resume_page_meta'; +SET DEBUG_SYNC = 'clone_redo_copy SIGNAL start_ddl_redo WAIT_FOR resume_redo'; +--source ../include/clone_command_send.inc + +if ($ddl_file_copy) { + --let $skip_ddl = +} + +connect (con1,localhost,root,,); + +--echo # In connection CON1 + +if (!$remote_clone) { + --echo # Waiting for clone to reach 'Initial metadata transfer' + SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_file_init'; + --echo # Wait finished +} + +--let $table_num = 01 + +if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc +} + +if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = +} + +SET DEBUG_SYNC = 'now SIGNAL resume_file_init'; + +--echo # Waiting for clone to reach 'file copy' +SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_file'; +--echo # Wait finished + +--let $table_num = 1 + +if($remote_clone) { + --echo # For remote clone, let the donor progress concurrently + --echo # otherwise file pins may not be released causing deadlock + SET DEBUG_SYNC = 'clone_notify_ddl SIGNAL resume_file'; +} + +if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc +} + +--eval $dml_stmt1 + +--echo # Flush all dirty pages to track +SET GLOBAL innodb_buf_flush_list_now = 1; + +if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = +} + +SET DEBUG_SYNC = 'now SIGNAL resume_file'; + +# For remote clone, DDL is executed concurrently. This is to +# make sure that clone doesn't enter into page copy state +SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_file_ack'; +SET DEBUG_SYNC = 'now SIGNAL resume_file_ack'; + +if (!$remote_clone) { + --echo # Waiting for clone to reach DDL metadata for 'file copy' + SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_file_meta'; + --echo # Wait finished +} + +if(!$in_place_ddl) { + --let $table_num = 11 + + if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc + } + + --eval $dml_stmt11 + + if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = + } +} + +SET DEBUG_SYNC = 'now SIGNAL resume_file_meta'; + +--echo # Waiting for clone to reach 'page copy' +SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_page'; +--echo # Wait finished + +if ($ddl_page_copy) { + --let $skip_ddl = +} + +# In place DDL currently waits for page copy to complete. Allow +# clone to proceed concurrently. + +if($in_place_ddl) { + SET DEBUG_SYNC = 'clone_notify_ddl SIGNAL resume_page'; +} + +--let $table_num = 2 + +if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc +} + +--eval $dml_stmt2 + +if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = +} + +SET DEBUG_SYNC = 'now SIGNAL resume_page'; + +if (!$remote_clone) { + --echo # Waiting for clone to reach DDL metadata for 'page copy' + SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_page_meta'; + --echo # Wait finished +} + +if ($remote_clone) { + --echo # Waiting for clone to reach 'redo copy' + SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_redo'; + --echo # Wait finished +} + +--let $table_num = 21 + +if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc +} + +--eval $dml_stmt21 + +if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = +} + +if (!$remote_clone) { + SET DEBUG_SYNC = 'now SIGNAL resume_page_meta'; + + --echo # Waiting for clone to reach 'redo copy' + SET DEBUG_SYNC = 'now WAIT_FOR start_ddl_redo'; + --echo # Wait finished +} + +--let $table_num = 3 + +if (!$skip_ddl) { + --source ../include/clone_exec_ddl.inc +} + +if ($post_dml) { + --let $after_dml = _after_dml + --source ../include/clone_exec_ddl.inc + --let $after_dml = +} + +SET DEBUG_SYNC = 'now SIGNAL resume_redo'; + +connection default; +--echo # In connection DEFAULT +--echo # Waiting for clone to complete +--reap +--echo # Wait finished + +if ($remote_clone) { + SET GLOBAL DEBUG = '-d,remote_release_clone_file_pin'; +} + +if (!$remote_clone) { + SET GLOBAL DEBUG = '-d,local_release_clone_file_pin'; +} + +--disconnect con1 +--source include/wait_until_count_sessions.inc + +--echo # Restart server on cloned data directory +if ($ddl_encryption) { + --let keyring_status=`SELECT STATUS_VALUE FROM performance_schema.keyring_component_status WHERE STATUS_KEY = "Component_status"` + if (!$keyring_status){ + --source suite/component_keyring_file/inc/setup_component_customized.inc + } + --replace_result $CLONE_DATADIR CLONE_DATADIR $MYSQL_TMP_DIR MYSQL_TMP_DIR $PLUGIN_DIR_OPT PLUGIN_DIR_OPT + --let restart_parameters="restart: --datadir=$CLONE_DATADIR $PLUGIN_DIR_OPT" +} + +if (!$ddl_encryption) { + --replace_result $CLONE_DATADIR CLONE_DATADIR + --let restart_parameters="restart: --datadir=$CLONE_DATADIR" +} + +--source include/restart_mysqld.inc + +--let $ddl_exec = + +--echo # Check cloned data +SHOW TABLES; + +if (!$ddl_skip_check) { + --let $ddl_show = 1 + + --let $table_num = 1 + --source ../include/clone_exec_ddl.inc + + --let $table_num = 11 + --source ../include/clone_exec_ddl.inc + + --let $table_num = 2 + --source ../include/clone_exec_ddl.inc + + --let $ddl_show = + + if ($dml_stmt_check) { + --eval $dml_stmt_check + } +} + +--echo # Restart server back on base data directory + +if ($ddl_encryption) { + --replace_result $MYSQL_TMP_DIR MYSQL_TMP_DIR $ENCRYPT_DATADIR ENCRYPT_DATADIR $PLUGIN_DIR_OPT PLUGIN_DIR_OPT + --let restart_parameters=restart: --datadir=$ENCRYPT_DATADIR $PLUGIN_DIR_OPT +} + +if (!$ddl_encryption) { + + if (!$skip_space_validation) { + --let restart_parameters=restart: + } + + if ($skip_space_validation) { + --let restart_parameters=restart: --skip-innodb-validate-tablespace-paths + } +} + +--source include/restart_mysqld.inc + +if ($undo_encryption) { + SET GLOBAL innodb_undo_log_encrypt = ON; + SHOW VARIABLES LIKE 'innodb_undo_log_encrypt'; +} + +--force-rmdir $CLONE_DATADIR +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/include/ddl_kill.inc b/mysql-test/suite/clone/include/ddl_kill.inc new file mode 100644 index 0000000000000..115d369bd2ecc --- /dev/null +++ b/mysql-test/suite/clone/include/ddl_kill.inc @@ -0,0 +1,33 @@ +## KILL DDL command while waiting for clone + +--let $CON_ID= `SELECT CONNECTION_ID()` + +SET DEBUG_SYNC = 'clone_notify_ddl SIGNAL kill_ddl'; +--send_eval $ddl_stmt + +--connection con1 +--echo # In connection CON1 + +--echo # Waiting for DDL to reach notification +SET DEBUG_SYNC = 'now WAIT_FOR kill_ddl'; +--echo # Wait finished + +--replace_result $CON_ID CON_ID +--eval KILL QUERY $CON_ID + +--connection con2 +--echo # In connection CON2 + +if (!$no_interrupt) { + --echo # Waiting for DDL to exit with error + --error ER_QUERY_INTERRUPTED + --reap +} + +--echo # Query should no longer be interrupted and should pass +if ($no_interrupt) { + --reap +} + +--echo # Wait finished +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/include/ddl_lock_clone_wait.inc b/mysql-test/suite/clone/include/ddl_lock_clone_wait.inc new file mode 100644 index 0000000000000..33fe5550f0107 --- /dev/null +++ b/mysql-test/suite/clone/include/ddl_lock_clone_wait.inc @@ -0,0 +1,26 @@ +## Clone DDL lock timeout + +--connection con1 +--echo # In connection CON1 + +SET DEBUG_SYNC = 'clone_notify_ddl SIGNAL resume_clone WAIT_FOR resume_ddl'; +--send_eval $ddl_text + +--connection default +--echo # In connection DEFAULT +SET DEBUG_SYNC = 'now WAIT_FOR resume_clone'; + +--let $clone_err = ER_LOCK_WAIT_TIMEOUT +--let $clone_remote_err = ER_CLONE_DONOR +--source ../include/clone_command.inc +--let $clone_err = 0 +--let $clone_remote_err = 0 + +SET DEBUG_SYNC = 'now SIGNAL resume_ddl'; + +--connection con1 +--echo # In connection CON1 +--reap + +--connection default +--echo # In connection DEFAULT diff --git a/mysql-test/suite/clone/include/drop_schema.inc b/mysql-test/suite/clone/include/drop_schema.inc new file mode 100644 index 0000000000000..b982a42afce9f --- /dev/null +++ b/mysql-test/suite/clone/include/drop_schema.inc @@ -0,0 +1,9 @@ +## Drop test schema +DROP TABLE t1; +DROP TABLE t2; + +DROP PROCEDURE execute_dml; + +if ($clone_ddl) { + DROP PROCEDURE execute_ddl; +} diff --git a/mysql-test/suite/clone/r/aria_basic.result b/mysql-test/suite/clone/r/aria_basic.result new file mode 100644 index 0000000000000..31b430d0bd6c9 --- /dev/null +++ b/mysql-test/suite/clone/r/aria_basic.result @@ -0,0 +1,196 @@ +### +# Test for mix of online/offline backup tables +##### +CREATE TABLE t_default(i INT PRIMARY KEY) +ENGINE ARIA; +INSERT INTO t_default VALUES (1); +CREATE TABLE t_tr_p_ch(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO t_tr_p_ch VALUES (1); +CREATE TABLE t_tr_p_nch(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=0; +INSERT INTO t_tr_p_nch VALUES (1); +CREATE TABLE t_p_ch(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO t_p_ch VALUES (1); +CREATE TABLE t_p_nch(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=PAGE PAGE_CHECKSUM=0; +INSERT INTO t_p_nch VALUES (1); +CREATE TABLE t_fixed(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=FIXED PAGE_CHECKSUM=1; +INSERT INTO t_fixed VALUES (1); +CREATE TABLE t_dyn(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=DYNAMIC PAGE_CHECKSUM=1; +INSERT INTO t_dyn VALUES (1); +# Test for partitioned table +CREATE TABLE t_part_online(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL = 1 PAGE_CHECKSUM = 1 +PARTITION BY RANGE( i ) ( +PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20), +PARTITION p2 VALUES LESS THAN (30) +); +INSERT INTO t_part_online VALUES(5); +INSERT INTO t_part_online VALUES(15); +INSERT INTO t_part_online VALUES(25); +SELECT * FROM t_part_online; +i +5 +15 +25 +CREATE TABLE t_part_offline(i INT) +ENGINE ARIA TRANSACTIONAL = 0 PAGE_CHECKSUM = 0 +PARTITION BY RANGE( i ) ( +PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20), +PARTITION p2 VALUES LESS THAN (30) +); +INSERT INTO t_part_offline VALUES(5); +INSERT INTO t_part_offline VALUES(15); +INSERT INTO t_part_offline VALUES(25); +# Test for filename to tablename mapping +CREATE TABLE `t 1 t-1`(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO `t 1 t-1` VALUES (1); +CREATE TABLE `t-part online`(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL = 1 PAGE_CHECKSUM = 1 +PARTITION BY RANGE( i ) ( +PARTITION p0 VALUES LESS THAN (10), +PARTITION p1 VALUES LESS THAN (20), +PARTITION p2 VALUES LESS THAN (30) +); +INSERT INTO `t-part online` VALUES(5); +INSERT INTO `t-part online` VALUES(15); +INSERT INTO `t-part online` VALUES(25); +### +# Test for redo log files backup; +##### +CREATE TABLE t_logs_1(i INT) +ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +CREATE TABLE t_logs_2 LIKE t_logs_1; +CREATE TABLE t_bulk_ins LIKE t_logs_1; +INSERT INTO t_logs_1 VALUES +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9); +# Generate several log files +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +### +# Test for DML during backup for online backup +##### +CREATE TABLE t_dml(i INT PRIMARY KEY) +ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO t_dml VALUES(1), (2), (3); +SET SESSION debug_dbug="+d,maria_flush_whole_log"; +SET GLOBAL aria_checkpoint_interval=10000; +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +connection clone_conn_1; +SET DEBUG_SYNC= 'after_aria_table_copy_t_dml SIGNAL dml_start WAIT_FOR aria_1'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection default; +set DEBUG_SYNC="now WAIT_FOR dml_start"; +DELETE FROM test.t_dml where i = 3; +UPDATE test.t_dml SET i = 4 where i = 1; +INSERT INTO test.t_dml VALUES(5); +SELECT * FROM test.t_dml; +i +2 +4 +5 +SET DEBUG_SYNC="now SIGNAL aria_1"; +connection clone_conn_1; +connection default; +# Restart server on cloned data directory +# restart: with restart_parameters +### Result for DML test +SELECT * FROM t_dml; +i +2 +4 +5 +### Result for redo log files backup +# ok +# ok +# ok +# restart +### Clean up for DML test +DROP TABLE t_dml; +### Cleanup for redo log files backup +DROP TABLE t_logs_1; +DROP TABLE t_logs_2; +DROP TABLE t_bulk_ins; +### Result for online/offline tables test +SELECT * FROM t_default; +i +1 +SELECT * FROM t_tr_p_ch; +i +1 +SELECT * FROM t_tr_p_nch; +i +1 +SELECT * FROM t_p_ch; +i +1 +SELECT * FROM t_p_nch; +i +1 +SELECT * FROM t_fixed; +i +1 +SELECT * FROM t_dyn; +i +1 +SELECT * FROM t_part_online; +i +5 +15 +25 +SELECT * FROM t_part_offline; +i +5 +15 +25 +SELECT * FROM `t 1 t-1`; +i +1 +SELECT * FROM `t-part online`; +i +5 +15 +25 +### Cleanup for online/offline tables test +DROP TABLE t_default; +DROP TABLE t_tr_p_ch; +DROP TABLE t_tr_p_nch; +DROP TABLE t_p_ch; +DROP TABLE t_p_nch; +DROP TABLE t_fixed; +DROP TABLE t_dyn; +DROP TABLE t_part_online; +DROP TABLE t_part_offline; +DROP TABLE `t 1 t-1`; +DROP TABLE `t-part online`; +disconnect clone_conn_1; +connection default; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/backup_stage_and_lock.result b/mysql-test/suite/clone/r/backup_stage_and_lock.result new file mode 100644 index 0000000000000..c8556002ed298 --- /dev/null +++ b/mysql-test/suite/clone/r/backup_stage_and_lock.result @@ -0,0 +1,27 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SET DEBUG_SYNC="backup_stage_start SIGNAL start_con1 WAIT_FOR res_clone1"; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connect con1,localhost,root,,,; +SET DEBUG_SYNC="now WAIT_FOR start_con1"; +SET lock_wait_timeout=1; +BACKUP STAGE START; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +SET DEBUG_SYNC="now SIGNAL res_clone1"; +connection default; +BACKUP STAGE START; +SET lock_wait_timeout=1; +connection con1; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: Lock wait timeout exceeded; try restarting transaction +connection default; +BACKUP STAGE END; +SET DEBUG_SYNC="clone_backup_lock SIGNAL con1_wait WAIT_FOR res_clone2"; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection con1; +set DEBUG_SYNC="now WAIT_FOR con1_wait"; +BACKUP LOCK mysql.table_stats; +BACKUP UNLOCK; +SET DEBUG_SYNC="now SIGNAL res_clone2"; +connection default; +disconnect con1; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/error_archival.result b/mysql-test/suite/clone/r/error_archival.result new file mode 100644 index 0000000000000..16493c5e8472b --- /dev/null +++ b/mysql-test/suite/clone/r/error_archival.result @@ -0,0 +1,339 @@ +call mtr.add_suppression("\\[ERROR\\] InnoDB: Log writer waited too long for archiver"); +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 100, 100, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +# Test-1: Error during redo archival +# In connection default - Cloning database +SET GLOBAL DEBUG = '+d,clone_redo_archive_error'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 100 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +ERROR HY000: Internal error: Clone: Log Archiver failed +SET GLOBAL DEBUG = '-d,clone_redo_archive_error'; +SET DEBUG_SYNC = 'RESET'; +# Test-2: Error overwrite redo archival data +# In connection default - Cloning database +SET GLOBAL DEBUG = '+d,clone_redo_no_archive'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Delete all rows +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +# In connection con1 - Insert 200 rows +call execute_dml(0, 0, 200, 200, 10, 0); +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +ERROR HY000: Internal error: Clone: Log Archiver failed +SET GLOBAL DEBUG = '-d,clone_redo_no_archive'; +SET DEBUG_SYNC = 'RESET'; +# Test-3: Successful clone after archival error +# In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 100 Key Range] +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +SET DEBUG_SYNC = 'RESET'; +disconnect con1; +# Restart server on cloned data directory +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +200 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +199 Clone Test Row - 199 umn Data Large Column Data Large +198 Clone Test Row - 198 umn Data Large Column Data Large +197 Clone Test Row - 197 umn Data Large Column Data Large +196 Clone Test Row - 196 umn Data Large Column Data Large +195 Clone Test Row - 195 umn Data Large Column Data Large +194 Clone Test Row - 194 umn Data Large Column Data Large +193 Clone Test Row - 193 umn Data Large Column Data Large +192 Clone Test Row - 192 umn Data Large Column Data Large +191 Clone Test Row - 191 umn Data Large Column Data Large +190 Clone Test Row - 190 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +200 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +199 Clone Test Row - 199 umn Data Large Column Data Large +198 Clone Test Row - 198 umn Data Large Column Data Large +197 Clone Test Row - 197 umn Data Large Column Data Large +196 Clone Test Row - 196 umn Data Large Column Data Large +195 Clone Test Row - 195 umn Data Large Column Data Large +194 Clone Test Row - 194 umn Data Large Column Data Large +193 Clone Test Row - 193 umn Data Large Column Data Large +192 Clone Test Row - 192 umn Data Large Column Data Large +191 Clone Test Row - 191 umn Data Large Column Data Large +190 Clone Test Row - 190 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/error_basic.result b/mysql-test/suite/clone/r/error_basic.result new file mode 100644 index 0000000000000..9be7a4a495500 --- /dev/null +++ b/mysql-test/suite/clone/r/error_basic.result @@ -0,0 +1,26 @@ +# 1. PLUGIN not loaded - clone local +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: Plugin 'clone' is not loaded +# 1A. PLUGIN not installed - Uninstall plugin +UNINSTALL PLUGIN clone; +ERROR 42000: PLUGIN clone does not exist +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +# 1B. PLUGIN already loaded - Install plugin +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +ERROR HY000: Plugin 'clone' already installed +#1C. Clone data without error +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# 2A. Incorrect PATH - Relative path +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: Incorrect path value: 'CLONE_DATADIR' +# 2B. Incorrect PATH - Too long +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: The path specified for DATA DIRECTORY is too long +# 2C. Incorrect PATH - Within data directory +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: Path is within the current data directory 'CLONE_DATADIR' +# 2D. Incorrect PATH - data directory exists +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: Can't create database 'CLONE_DATADIR'; database exists +#Cleanup +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/error_features.result b/mysql-test/suite/clone/r/error_features.result new file mode 100644 index 0000000000000..08935d4eb0c72 --- /dev/null +++ b/mysql-test/suite/clone/r/error_features.result @@ -0,0 +1,55 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE DATABASE testdb_clone; +CREATE USER 'user_clone'@'localhost' IDENTIFIED BY '123'; +GRANT ALL ON testdb_clone.* TO 'user_clone'@'localhost'; +GRANT SELECT ON performance_schema.* to 'user_clone'@'localhost'; +SHOW GRANTS FOR 'user_clone'@'localhost'; +Grants for user_clone@localhost +GRANT USAGE ON *.* TO `user_clone`@`localhost` IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' +GRANT ALL PRIVILEGES ON `testdb_clone`.* TO `user_clone`@`localhost` +GRANT SELECT ON `performance_schema`.* TO `user_clone`@`localhost` +# Connection without NECESSARY privilege +connect con1,'localhost','user_clone','123',; +SELECT user(); +user() +user_clone@localhost +USE testdb_clone; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 char(64)); +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR 42000: Access denied; you need (at least one of) the RELOAD privilege(s) for this operation +connection default; +SHOW GRANTS FOR 'user_clone'@'localhost'; +Grants for user_clone@localhost +GRANT USAGE ON *.* TO `user_clone`@`localhost` IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' +GRANT ALL PRIVILEGES ON `testdb_clone`.* TO `user_clone`@`localhost` +GRANT SELECT ON `performance_schema`.* TO `user_clone`@`localhost` +# Grant backup privilege to clone user +GRANT RELOAD on *.* to 'user_clone'@'localhost'; +FLUSH PRIVILEGES; +SHOW GRANTS FOR 'user_clone'@'localhost'; +Grants for user_clone@localhost +GRANT RELOAD ON *.* TO `user_clone`@`localhost` IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' +GRANT ALL PRIVILEGES ON `testdb_clone`.* TO `user_clone`@`localhost` +GRANT SELECT ON `performance_schema`.* TO `user_clone`@`localhost` +disconnect con1; +# Without LOCK_TBL privilege +connect con1,'localhost','user_clone','123',; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR 42000: Access denied; you need (at least one of) the LOCK TABLES privilege(s) for this operation +connection default; +disconnect con1; +GRANT LOCK TABLES ON *.* TO 'user_clone'@'localhost'; +FLUSH PRIVILEGES; +SHOW GRANTS FOR 'user_clone'@'localhost'; +Grants for user_clone@localhost +GRANT RELOAD, LOCK TABLES ON *.* TO `user_clone`@`localhost` IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' +GRANT ALL PRIVILEGES ON `testdb_clone`.* TO `user_clone`@`localhost` +GRANT SELECT ON `performance_schema`.* TO `user_clone`@`localhost` +# Trying clone again with all privileges +connect con1,'localhost','user_clone','123',; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection default; +disconnect con1; +DROP SCHEMA testdb_clone; +DROP USER 'user_clone'@'localhost'; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_aria_log_dir_path.result b/mysql-test/suite/clone/r/local_aria_log_dir_path.result new file mode 100644 index 0000000000000..8f51d2eca36af --- /dev/null +++ b/mysql-test/suite/clone/r/local_aria_log_dir_path.result @@ -0,0 +1,34 @@ +# Restart mariadbd with the test specific parameters +# restart: with restart_parameters +# Create and populate an Aria table (and Aria logs) +CREATE TABLE t1 (id INT, txt LONGTEXT) ENGINE=Aria; +BEGIN NOT ATOMIC +FOR id IN 0..9 DO +INSERT INTO test.t1 (id, txt) VALUES (id, REPEAT(id,1024*1024)); +END FOR; +END; +$$ +# Testing aria log files before --backup +SET @@global.aria_checkpoint_interval=DEFAULT /*Force checkpoint*/; +SHOW ENGINE aria logs; +Type Name Status +Aria aria_log.00000001 free +Aria aria_log.00000002 in use +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# restart: with restart_parameters +# Check that the table is there after cloning +SELECT COUNT(*) from t1; +COUNT(*) +10 +DROP TABLE t1; +# Testing aria log files after clone +SET @@global.aria_checkpoint_interval=DEFAULT /*Force checkpoint*/; +SHOW ENGINE aria logs; +Type Name Status +Aria aria_log.00000001 free +Aria aria_log.00000002 in use +# Restarting mariadbd with default parameters +# restart +DROP TABLE t1; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_aria_log_tables.result b/mysql-test/suite/clone/r/local_aria_log_tables.result new file mode 100644 index 0000000000000..7cb8600515d31 --- /dev/null +++ b/mysql-test/suite/clone/r/local_aria_log_tables.result @@ -0,0 +1,36 @@ +CREATE TABLE t(i INT) +ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +SET GLOBAL general_log = 0; +TRUNCATE mysql.general_log; +SET GLOBAL general_log = 1; +SET GLOBAL log_output = 'TABLE'; +INSERT INTO t VALUES (1); +SELECT * FROM mysql.general_log +WHERE argument LIKE "INSERT INTO %" AND +(command_type = "Query" OR command_type = "Execute") ; +event_time user_host thread_id server_id command_type argument +TIMESTAMP USER_HOST THREAD_ID 1 Query INSERT INTO t VALUES (1) +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +SET DEBUG_SYNC="after_stage_block_ddl SIGNAL start_dml WAIT_FOR resume_clone1"; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Insert new row into general_log table after it has been copied on BLOCK_DDL. +connect con1,localhost,root,,; +SET DEBUG_SYNC="now WAIT_FOR start_dml"; +INSERT INTO test.t VALUES(2); +SET DEBUG_SYNC="now SIGNAL resume_clone1"; +connection default; +disconnect con1; +# restart: with restart_parameters +SELECT * FROM mysql.general_log +WHERE argument LIKE "INSERT INTO %" AND +(command_type = "Query" OR command_type = "Execute") ; +event_time user_host thread_id server_id command_type argument +TIMESTAMP USER_HOST THREAD_ID 1 Query INSERT INTO t VALUES (1) +TIMESTAMP USER_HOST THREAD_ID 1 Query INSERT INTO test.t VALUES(2) +# restart +DROP TABLE t; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_aria_rotate_during_backup.result b/mysql-test/suite/clone/r/local_aria_rotate_during_backup.result new file mode 100644 index 0000000000000..86c3bf2a82767 --- /dev/null +++ b/mysql-test/suite/clone/r/local_aria_rotate_during_backup.result @@ -0,0 +1,63 @@ +SHOW VARIABLES LIKE 'aria_log_file_size'; +Variable_name Value +aria_log_file_size 8388608 +CREATE PROCEDURE display_aria_log_control(ctrl BLOB) +BEGIN +SELECT HEX(REVERSE(SUBSTRING(ctrl, 42, 4))) AS last_logno; +END; +$$ +CREATE PROCEDURE populate_t1() +BEGIN +FOR id IN 0..9 DO +INSERT INTO test.t1 (id, txt) VALUES (id, REPEAT(id,1024*1024)); +END FOR; +END; +$$ +CREATE TABLE test.t1(id INT, txt LONGTEXT) ENGINE=Aria; +# MYSQLD_DATADIR/aria_log_control before --backup +CALL display_aria_log_control(@aria_log_control); +last_logno +00000001 +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +SET DEBUG_SYNC = 'after_scanning_log_files SIGNAL start_dml1 WAIT_FOR resume_clone1'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connect con1, localhost,root,,; +SET DEBUG_SYNC= 'now WAIT_FOR start_dml1'; +CALL test.populate_t1(); +SET DEBUG_SYNC= 'now SIGNAL resume_clone1'; +connection default; +disconnect con1; +CALL display_aria_log_control(@aria_log_control); +last_logno +00000002 +# targetdir/aria_log_control after cloning +CALL display_aria_log_control(@aria_log_control); +last_logno +00000002 +# restart: with restart_parameters +# MYSQLD_DATADIR/aria_log_control after restart with clone +CALL display_aria_log_control(@aria_log_control); +last_logno +00000002 +# Checking that after --restore all t1 data is there +SELECT id, LENGTH(txt) FROM t1 ORDER BY id; +id LENGTH(txt) +0 1048576 +1 1048576 +2 1048576 +3 1048576 +4 1048576 +5 1048576 +6 1048576 +7 1048576 +8 1048576 +9 1048576 +# restart +DROP TABLE t1; +DROP PROCEDURE populate_t1; +DROP PROCEDURE display_aria_log_control; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_basic.result b/mysql-test/suite/clone/r/local_basic.result new file mode 100644 index 0000000000000..fb3e2f9aaa861 --- /dev/null +++ b/mysql-test/suite/clone/r/local_basic.result @@ -0,0 +1,126 @@ +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 char(64), FULLTEXT KEY fts_index(col2))ENGINE=InnoDB; +INSERT INTO t1 VALUES(10, 'clone row 1'); +INSERT INTO t1 VALUES(20, 'clone row 2'); +INSERT INTO t1 VALUES(30, 'clone row 3'); +SELECT * from t1 ORDER BY col1; +col1 col2 +10 clone row 1 +20 clone row 2 +30 clone row 3 +SELECT count(*) FROM mysql.general_log; +count(*) +0 +SELECT count(*) FROM mysql.slow_log; +count(*) +0 +CREATE TABLE t_myisam(col1 INT PRIMARY KEY, col2 char(64)) ENGINE=MyISAM; +INSERT INTO t_myisam VALUES(10, 'myisam not cloned row 1'); +SELECT * from t_myisam ORDER BY col1; +col1 col2 +10 myisam not cloned row 1 +CREATE TABLE t_csv(col1 INT NOT NULL, col2 char(64) NOT NULL) ENGINE=CSV; +INSERT INTO t_csv VALUES(10, 'csv not cloned row 1'); +SELECT * from t_csv ORDER BY col1; +col1 col2 +10 csv not cloned row 1 +CREATE SCHEMA non_innodb; +CREATE TABLE non_innodb.t_myisam(col1 INT PRIMARY KEY, col2 char(64)) ENGINE=MyISAM; +INSERT INTO non_innodb.t_myisam VALUES(10, 'myisam not cloned row 1'); +INSERT INTO non_innodb.t_myisam VALUES(20, 'myisam not cloned row 2'); +INSERT INTO non_innodb.t_myisam VALUES(30, 'myisam not cloned row 3'); +SELECT * from non_innodb.t_myisam ORDER BY col1; +col1 col2 +10 myisam not cloned row 1 +20 myisam not cloned row 2 +30 myisam not cloned row 3 +CREATE TABLE non_innodb.t_csv(col1 INT NOT NULL, col2 char(64) NOT NULL) ENGINE=CSV; +INSERT INTO non_innodb.t_csv VALUES(10, 'csv not cloned row 1'); +INSERT INTO non_innodb.t_csv VALUES(20, 'csv not cloned row 2'); +INSERT INTO non_innodb.t_csv VALUES(30, 'csv not cloned row 3'); +SELECT * from non_innodb.t_csv ORDER BY col1; +col1 col2 +10 csv not cloned row 1 +20 csv not cloned row 2 +30 csv not cloned row 3 +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection default; +# Restart server on cloned data directory +# restart: with restart_parameters +SELECT * from t1 ORDER BY col1; +col1 col2 +10 clone row 1 +20 clone row 2 +30 clone row 3 +INSERT INTO t1 VALUES(40, 'clone row 4'); +SELECT * from t1 ORDER BY col1; +col1 col2 +10 clone row 1 +20 clone row 2 +30 clone row 3 +40 clone row 4 +SELECT * from t_myisam ORDER BY col1; +col1 col2 +10 myisam not cloned row 1 +INSERT INTO t_myisam VALUES(40, 'myisam not cloned row 4'); +SELECT * from t_myisam ORDER BY col1; +col1 col2 +10 myisam not cloned row 1 +40 myisam not cloned row 4 +INSERT INTO t_csv VALUES(40, 'csv not cloned row 4'); +SELECT * from t_csv ORDER BY col1; +col1 col2 +10 csv not cloned row 1 +40 csv not cloned row 4 +INSERT INTO non_innodb.t_myisam VALUES(40, 'myisam not cloned row 4'); +SELECT * from non_innodb.t_myisam ORDER BY col1; +col1 col2 +10 myisam not cloned row 1 +20 myisam not cloned row 2 +30 myisam not cloned row 3 +40 myisam not cloned row 4 +INSERT INTO non_innodb.t_csv VALUES(40, 'csv not cloned row 4'); +SELECT * from non_innodb.t_csv ORDER BY col1; +col1 col2 +10 csv not cloned row 1 +20 csv not cloned row 2 +30 csv not cloned row 3 +40 csv not cloned row 4 +SHOW TABLES; +Tables_in_test +t1 +t_csv +t_myisam +SELECT count(*) FROM mysql.general_log; +count(*) +0 +SELECT count(*) FROM mysql.slow_log; +count(*) +0 +SET GLOBAL general_log = ON; +SET GLOBAL slow_query_log = ON; +# restart +SHOW TABLES; +Tables_in_test +t1 +t_csv +t_myisam +SELECT * from t1 ORDER BY col1; +col1 col2 +10 clone row 1 +20 clone row 2 +30 clone row 3 +SELECT * from t_myisam ORDER BY col1; +col1 col2 +10 myisam not cloned row 1 +DROP TABLE t1; +DROP TABLE t_myisam; +DROP TABLE t_csv; +DROP TABLE non_innodb.t_myisam; +DROP TABLE non_innodb.t_csv; +DROP SCHEMA non_innodb; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_boundary.result b/mysql-test/suite/clone/r/local_boundary.result new file mode 100644 index 0000000000000..e68222988fa0a --- /dev/null +++ b/mysql-test/suite/clone/r/local_boundary.result @@ -0,0 +1,405 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 150, 150, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +150 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +149 1490 Clone Test Row - 149 umn Data Large Column Data Large +148 1480 Clone Test Row - 148 umn Data Large Column Data Large +147 1470 Clone Test Row - 147 umn Data Large Column Data Large +146 1460 Clone Test Row - 146 umn Data Large Column Data Large +145 1450 Clone Test Row - 145 umn Data Large Column Data Large +144 1440 Clone Test Row - 144 umn Data Large Column Data Large +143 1430 Clone Test Row - 143 umn Data Large Column Data Large +142 1420 Clone Test Row - 142 umn Data Large Column Data Large +141 1410 Clone Test Row - 141 umn Data Large Column Data Large +140 1400 Clone Test Row - 140 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +150 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +149 1490 Clone Test Row - 149 umn Data Large Column Data Large +148 1480 Clone Test Row - 148 umn Data Large Column Data Large +147 1470 Clone Test Row - 147 umn Data Large Column Data Large +146 1460 Clone Test Row - 146 umn Data Large Column Data Large +145 1450 Clone Test Row - 145 umn Data Large Column Data Large +144 1440 Clone Test Row - 144 umn Data Large Column Data Large +143 1430 Clone Test Row - 143 umn Data Large Column Data Large +142 1420 Clone Test Row - 142 umn Data Large Column Data Large +141 1410 Clone Test Row - 141 umn Data Large Column Data Large +140 1400 Clone Test Row - 140 umn Data Large Column Data Large +# In connection default - Cloning database +SET GLOBAL DEBUG ="+d,clone_no_zero_copy"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 150 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 150, 200, 100, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 150, 200, 100, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +SET GLOBAL DEBUG ="-d,clone_no_zero_copy"; +disconnect con1; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +150 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +149 Clone Test Row - 149 umn Data Large Column Data Large +148 Clone Test Row - 148 umn Data Large Column Data Large +147 Clone Test Row - 147 umn Data Large Column Data Large +146 Clone Test Row - 146 umn Data Large Column Data Large +145 Clone Test Row - 145 umn Data Large Column Data Large +144 Clone Test Row - 144 umn Data Large Column Data Large +143 Clone Test Row - 143 umn Data Large Column Data Large +142 Clone Test Row - 142 umn Data Large Column Data Large +141 Clone Test Row - 141 umn Data Large Column Data Large +140 Clone Test Row - 140 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +150 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +149 Clone Test Row - 149 umn Data Large Column Data Large +148 Clone Test Row - 148 umn Data Large Column Data Large +147 Clone Test Row - 147 umn Data Large Column Data Large +146 Clone Test Row - 146 umn Data Large Column Data Large +145 Clone Test Row - 145 umn Data Large Column Data Large +144 Clone Test Row - 144 umn Data Large Column Data Large +143 Clone Test Row - 143 umn Data Large Column Data Large +142 Clone Test Row - 142 umn Data Large Column Data Large +141 Clone Test Row - 141 umn Data Large Column Data Large +140 Clone Test Row - 140 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 150, 150, 100, 0); +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +149 1490 Clone Test Row - 149 umn Data Large Column Data Large +148 1480 Clone Test Row - 148 umn Data Large Column Data Large +147 1470 Clone Test Row - 147 umn Data Large Column Data Large +146 1460 Clone Test Row - 146 umn Data Large Column Data Large +145 1450 Clone Test Row - 145 umn Data Large Column Data Large +144 1440 Clone Test Row - 144 umn Data Large Column Data Large +143 1430 Clone Test Row - 143 umn Data Large Column Data Large +142 1420 Clone Test Row - 142 umn Data Large Column Data Large +141 1410 Clone Test Row - 141 umn Data Large Column Data Large +140 1400 Clone Test Row - 140 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +149 1490 Clone Test Row - 149 umn Data Large Column Data Large +148 1480 Clone Test Row - 148 umn Data Large Column Data Large +147 1470 Clone Test Row - 147 umn Data Large Column Data Large +146 1460 Clone Test Row - 146 umn Data Large Column Data Large +145 1450 Clone Test Row - 145 umn Data Large Column Data Large +144 1440 Clone Test Row - 144 umn Data Large Column Data Large +143 1430 Clone Test Row - 143 umn Data Large Column Data Large +142 1420 Clone Test Row - 142 umn Data Large Column Data Large +141 1410 Clone Test Row - 141 umn Data Large Column Data Large +140 1400 Clone Test Row - 140 umn Data Large Column Data Large +# restart +SET GLOBAL DEBUG ="+d,clone_arch_log_stop_file_end"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 10 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1 WAIT_FOR start_dml2'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 150, 150, 10, 0); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +SET GLOBAL DEBUG ="-d,clone_arch_log_stop_file_end"; +SET GLOBAL DEBUG ="+d,clone_arch_log_stop_file_end"; +SET GLOBAL DEBUG ="+d,clone_arch_log_extra_bytes"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 10 Key Range] +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1 WAIT_FOR start_dml2'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 150, 150, 10, 0); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +SET GLOBAL DEBUG ="-d,clone_arch_log_stop_file_end"; +SET GLOBAL DEBUG ="-d,clone_arch_log_extra_bytes"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 10 Key Range] +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; +connection default; +# In connection default - Cloning database +Got one of the listed errors +disconnect con1; +# restart +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 10 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 50, 50, 10, 0); +COMMIT; +connection default; +# In connection default - Cloning database +Got one of the listed errors +disconnect con1; +# restart +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +SET DEBUG_SYNC = 'clone_redo_copy SIGNAL start_dml3 WAIT_FOR resume_clone3'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 10 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 50, 50, 10, 0); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml3'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 50, 50, 10, 0); +COMMIT; +connection default; +# In connection default - Cloning database +Got one of the listed errors +disconnect con1; +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/local_ddl_for_common.result b/mysql-test/suite/clone/r/local_ddl_for_common.result new file mode 100644 index 0000000000000..da690dda17949 --- /dev/null +++ b/mysql-test/suite/clone/r/local_ddl_for_common.result @@ -0,0 +1,105 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS +FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +# ===== Engine iteration: CSV ===== +DROP TABLE IF EXISTS t1, t2, t3, t4, t5, +t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; +Warnings: +Note 1051 Unknown table 'test.t1,test.t2,test.t3,test.t4,test.t5,test.t1_m1,test.t1_m2,test.t2_m1,test.t2_m2,test.t3_m1,te...' +CREATE TABLE t1 (a INT NOT NULL) ENGINE=CSV; +CREATE TABLE t2 (a INT NOT NULL) ENGINE=CSV; +CREATE TABLE t3 (a INT NOT NULL) ENGINE=CSV; +CREATE TABLE t4 LIKE t1; +DROP TABLE t2; +RENAME TABLE t3 TO t5; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Restart server on cloned data directory +# restart: with restart_parameters +SELECT COUNT(*) FROM t4; +COUNT(*) +0 +SELECT * FROM t2; +ERROR 42S02: Table 'test.t2' doesn't exist +SELECT * FROM t3; +ERROR 42S02: Table 'test.t3' doesn't exist +SELECT COUNT(*) FROM t5; +COUNT(*) +0 +# restart +DROP TABLE IF EXISTS t4, t5, t1, t2, t3, +t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; +Warnings: +Note 1051 Unknown table 'test.t2,test.t3,test.t1_m1,test.t1_m2,test.t2_m1,test.t2_m2,test.t3_m1,test.t3_m2' +# ===== Engine iteration: MERGE ===== +DROP TABLE IF EXISTS t1, t2, t3, t4, t5, +t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; +Warnings: +Note 1051 Unknown table 'test.t1,test.t2,test.t3,test.t4,test.t5,test.t1_m1,test.t1_m2,test.t2_m1,test.t2_m2,test.t3_m1,te...' +CREATE TABLE t1_m1 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t1_m2 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t1 (a INT NOT NULL) +ENGINE=MERGE UNION=(t1_m1, t1_m2) INSERT_METHOD=LAST; +CREATE TABLE t2_m1 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t2_m2 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t2 (a INT NOT NULL) +ENGINE=MERGE UNION=(t2_m1, t2_m2) INSERT_METHOD=LAST; +CREATE TABLE t3_m1 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t3_m2 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t3 (a INT NOT NULL) +ENGINE=MERGE UNION=(t3_m1, t3_m2) INSERT_METHOD=LAST; +CREATE TABLE t4 LIKE t1; +DROP TABLE t2; +RENAME TABLE t3 TO t5; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Restart server on cloned data directory +# restart: with restart_parameters +SELECT COUNT(*) FROM t4; +COUNT(*) +0 +SELECT * FROM t2; +ERROR 42S02: Table 'test.t2' doesn't exist +SELECT * FROM t3; +ERROR 42S02: Table 'test.t3' doesn't exist +SELECT COUNT(*) FROM t5; +COUNT(*) +0 +# restart +DROP TABLE IF EXISTS t4, t5, t1, t2, t3, +t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; +Warnings: +Note 1051 Unknown table 'test.t2,test.t3' +# ===== Engine iteration: MyISAM ===== +DROP TABLE IF EXISTS t1, t2, t3, t4, t5, +t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; +Warnings: +Note 1051 Unknown table 'test.t1,test.t2,test.t3,test.t4,test.t5,test.t1_m1,test.t1_m2,test.t2_m1,test.t2_m2,test.t3_m1,te...' +CREATE TABLE t1 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t2 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t3 (a INT NOT NULL) ENGINE=MyISAM; +CREATE TABLE t4 LIKE t1; +DROP TABLE t2; +RENAME TABLE t3 TO t5; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Restart server on cloned data directory +# restart: with restart_parameters +SELECT COUNT(*) FROM t4; +COUNT(*) +0 +SELECT * FROM t2; +ERROR 42S02: Table 'test.t2' doesn't exist +SELECT * FROM t3; +ERROR 42S02: Table 'test.t3' doesn't exist +SELECT COUNT(*) FROM t5; +COUNT(*) +0 +# restart +DROP TABLE IF EXISTS t4, t5, t1, t2, t3, +t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; +Warnings: +Note 1051 Unknown table 'test.t2,test.t3,test.t1_m1,test.t1_m2,test.t2_m1,test.t2_m2,test.t3_m1,test.t3_m2' +UNINSTALL PLUGIN clone; +disconnect clone_conn_1; +connection default; diff --git a/mysql-test/suite/clone/r/local_dml.result b/mysql-test/suite/clone/r/local_dml.result new file mode 100644 index 0000000000000..10d823290a490 --- /dev/null +++ b/mysql-test/suite/clone/r/local_dml.result @@ -0,0 +1,338 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SHOW VARIABLES LIKE "clone_buffer_size"; +Variable_name Value +clone_buffer_size 4194304 +SET GLOBAL clone_buffer_size = 2097152; +SHOW VARIABLES LIKE "clone_buffer_size"; +Variable_name Value +clone_buffer_size 2097152 +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 100, 100, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col2, col3 FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 +0 0 Clone Test Row - 0 +1 10 Clone Test Row - 1 +2 20 Clone Test Row - 2 +3 30 Clone Test Row - 3 +4 40 Clone Test Row - 4 +5 50 Clone Test Row - 5 +6 60 Clone Test Row - 6 +7 70 Clone Test Row - 7 +8 80 Clone Test Row - 8 +9 90 Clone Test Row - 9 +SELECT col1, col2, col3 FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 +99 990 Clone Test Row - 99 +98 980 Clone Test Row - 98 +97 970 Clone Test Row - 97 +96 960 Clone Test Row - 96 +95 950 Clone Test Row - 95 +94 940 Clone Test Row - 94 +93 930 Clone Test Row - 93 +92 920 Clone Test Row - 92 +91 910 Clone Test Row - 91 +90 900 Clone Test Row - 90 +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col2, col3 FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 +0 0 Clone Test Row - 0 +1 10 Clone Test Row - 1 +2 20 Clone Test Row - 2 +3 30 Clone Test Row - 3 +4 40 Clone Test Row - 4 +5 50 Clone Test Row - 5 +6 60 Clone Test Row - 6 +7 70 Clone Test Row - 7 +8 80 Clone Test Row - 8 +9 90 Clone Test Row - 9 +SELECT col1, col2, col3 FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 +99 990 Clone Test Row - 99 +98 980 Clone Test Row - 98 +97 970 Clone Test Row - 97 +96 960 Clone Test Row - 96 +95 950 Clone Test Row - 95 +94 940 Clone Test Row - 94 +93 930 Clone Test Row - 93 +92 920 Clone Test Row - 92 +91 910 Clone Test Row - 91 +90 900 Clone Test Row - 90 +# In connection con1 - Running Insert Random [100 - 200 Key range] +connect con1,localhost,root,,; +call execute_dml(0, 100, 100, 100, 20, 1); +# In connection con2 - Running Update Random [0 - 25 Key Range] +connect con2,localhost,root,,; +call execute_dml(1, 0, 25, 100, 20, 1); +# In connection con3 - Running Delete Random [26 - 50 Key Range] +connect con3,localhost,root,,; +call execute_dml(2, 26, 25, 100, 20, 1); +# In connection con4 - Running Update Random uncommited [51 - 75 Key Range] +connect con4,localhost,root,,; +begin; +call execute_dml(1, 51, 25, 100, 200, 1); +# In connection default - Cloning database +connection clone_conn_1; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection default - Finished Cloning +# In connection con1 - Waiting +connection con1; +# In connection con2 - Waiting +connection con2; +# In connection con3 - Waiting +connection con3; +# In connection con4 - Waiting +connection con4; +commit; +# In connection default +connection default; +disconnect con1; +disconnect con2; +disconnect con3; +disconnect con4; +# Restart cloned database +# restart: with restart_parameters +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col2, col3 FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 +0 0 Clone Test Row - 0 +1 10 Clone Test Row - 1 +2 20 Clone Test Row - 2 +3 30 Clone Test Row - 3 +4 40 Clone Test Row - 4 +5 50 Clone Test Row - 5 +6 60 Clone Test Row - 6 +7 70 Clone Test Row - 7 +8 80 Clone Test Row - 8 +9 90 Clone Test Row - 9 +SELECT col1, col2, col3 FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 +99 990 Clone Test Row - 99 +98 980 Clone Test Row - 98 +97 970 Clone Test Row - 97 +96 960 Clone Test Row - 96 +95 950 Clone Test Row - 95 +94 940 Clone Test Row - 94 +93 930 Clone Test Row - 93 +92 920 Clone Test Row - 92 +91 910 Clone Test Row - 91 +90 900 Clone Test Row - 90 +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col2, col3 FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 +0 0 Clone Test Row - 0 +1 10 Clone Test Row - 1 +2 20 Clone Test Row - 2 +3 30 Clone Test Row - 3 +4 40 Clone Test Row - 4 +5 50 Clone Test Row - 5 +6 60 Clone Test Row - 6 +7 70 Clone Test Row - 7 +8 80 Clone Test Row - 8 +9 90 Clone Test Row - 9 +SELECT col1, col2, col3 FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 +99 990 Clone Test Row - 99 +98 980 Clone Test Row - 98 +97 970 Clone Test Row - 97 +96 960 Clone Test Row - 96 +95 950 Clone Test Row - 95 +94 940 Clone Test Row - 94 +93 930 Clone Test Row - 93 +92 920 Clone Test Row - 92 +91 910 Clone Test Row - 91 +90 900 Clone Test Row - 90 +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Restart cloned database +# restart: with restart_parameters +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col2, col3 FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 +0 0 Clone Test Row - 0 +1 10 Clone Test Row - 1 +2 20 Clone Test Row - 2 +3 30 Clone Test Row - 3 +4 40 Clone Test Row - 4 +5 50 Clone Test Row - 5 +6 60 Clone Test Row - 6 +7 70 Clone Test Row - 7 +8 80 Clone Test Row - 8 +9 90 Clone Test Row - 9 +SELECT col1, col2, col3 FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 +99 990 Clone Test Row - 99 +98 980 Clone Test Row - 98 +97 970 Clone Test Row - 97 +96 960 Clone Test Row - 96 +95 950 Clone Test Row - 95 +94 940 Clone Test Row - 94 +93 930 Clone Test Row - 93 +92 920 Clone Test Row - 92 +91 910 Clone Test Row - 91 +90 900 Clone Test Row - 90 +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col2, col3 FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 +0 0 Clone Test Row - 0 +1 10 Clone Test Row - 1 +2 20 Clone Test Row - 2 +3 30 Clone Test Row - 3 +4 40 Clone Test Row - 4 +5 50 Clone Test Row - 5 +6 60 Clone Test Row - 6 +7 70 Clone Test Row - 7 +8 80 Clone Test Row - 8 +9 90 Clone Test Row - 9 +SELECT col1, col2, col3 FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 +99 990 Clone Test Row - 99 +98 980 Clone Test Row - 98 +97 970 Clone Test Row - 97 +96 960 Clone Test Row - 96 +95 950 Clone Test Row - 95 +94 940 Clone Test Row - 94 +93 930 Clone Test Row - 93 +92 920 Clone Test Row - 92 +91 910 Clone Test Row - 91 +90 900 Clone Test Row - 90 +# restart +connection default; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +disconnect clone_conn_1; +connection default; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_encrypt_compress.result b/mysql-test/suite/clone/r/local_encrypt_compress.result new file mode 100644 index 0000000000000..10236997f7865 --- /dev/null +++ b/mysql-test/suite/clone/r/local_encrypt_compress.result @@ -0,0 +1,13 @@ +## Install plugin +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB PAGE_COMPRESSED=1 ENCRYPTED=YES; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB PAGE_COMPRESSED= 1; +CREATE TABLE t3(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), +col4 BLOB)ENGINE=InnoDB ENCRYPTED=YES; +# restart: --innodb_buffer_pool_load_at_startup=0 +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR 42000: This version of MariaDB doesn't yet support 'Encrypted and compressed tablespace ' +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_features.result b/mysql-test/suite/clone/r/local_features.result new file mode 100644 index 0000000000000..c21e5b3a09324 --- /dev/null +++ b/mysql-test/suite/clone/r/local_features.result @@ -0,0 +1,404 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 200, 200, 100, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +200 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +199 1990 Clone Test Row - 199 umn Data Large Column Data Large +198 1980 Clone Test Row - 198 umn Data Large Column Data Large +197 1970 Clone Test Row - 197 umn Data Large Column Data Large +196 1960 Clone Test Row - 196 umn Data Large Column Data Large +195 1950 Clone Test Row - 195 umn Data Large Column Data Large +194 1940 Clone Test Row - 194 umn Data Large Column Data Large +193 1930 Clone Test Row - 193 umn Data Large Column Data Large +192 1920 Clone Test Row - 192 umn Data Large Column Data Large +191 1910 Clone Test Row - 191 umn Data Large Column Data Large +190 1900 Clone Test Row - 190 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +200 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +199 1990 Clone Test Row - 199 umn Data Large Column Data Large +198 1980 Clone Test Row - 198 umn Data Large Column Data Large +197 1970 Clone Test Row - 197 umn Data Large Column Data Large +196 1960 Clone Test Row - 196 umn Data Large Column Data Large +195 1950 Clone Test Row - 195 umn Data Large Column Data Large +194 1940 Clone Test Row - 194 umn Data Large Column Data Large +193 1930 Clone Test Row - 193 umn Data Large Column Data Large +192 1920 Clone Test Row - 192 umn Data Large Column Data Large +191 1910 Clone Test Row - 191 umn Data Large Column Data Large +190 1900 Clone Test Row - 190 umn Data Large Column Data Large +SET GLOBAL innodb_buf_flush_list_now = 1; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +200 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +199 Clone Test Row - 199 umn Data Large Column Data Large +198 Clone Test Row - 198 umn Data Large Column Data Large +197 Clone Test Row - 197 umn Data Large Column Data Large +196 Clone Test Row - 196 umn Data Large Column Data Large +195 Clone Test Row - 195 umn Data Large Column Data Large +194 Clone Test Row - 194 umn Data Large Column Data Large +193 Clone Test Row - 193 umn Data Large Column Data Large +192 Clone Test Row - 192 umn Data Large Column Data Large +191 Clone Test Row - 191 umn Data Large Column Data Large +190 Clone Test Row - 190 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +200 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +199 Clone Test Row - 199 umn Data Large Column Data Large +198 Clone Test Row - 198 umn Data Large Column Data Large +197 Clone Test Row - 197 umn Data Large Column Data Large +196 Clone Test Row - 196 umn Data Large Column Data Large +195 Clone Test Row - 195 umn Data Large Column Data Large +194 Clone Test Row - 194 umn Data Large Column Data Large +193 Clone Test Row - 193 umn Data Large Column Data Large +192 Clone Test Row - 192 umn Data Large Column Data Large +191 Clone Test Row - 191 umn Data Large Column Data Large +190 Clone Test Row - 190 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 200, 200, 100, 0); +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +199 1990 Clone Test Row - 199 umn Data Large Column Data Large +198 1980 Clone Test Row - 198 umn Data Large Column Data Large +197 1970 Clone Test Row - 197 umn Data Large Column Data Large +196 1960 Clone Test Row - 196 umn Data Large Column Data Large +195 1950 Clone Test Row - 195 umn Data Large Column Data Large +194 1940 Clone Test Row - 194 umn Data Large Column Data Large +193 1930 Clone Test Row - 193 umn Data Large Column Data Large +192 1920 Clone Test Row - 192 umn Data Large Column Data Large +191 1910 Clone Test Row - 191 umn Data Large Column Data Large +190 1900 Clone Test Row - 190 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +# restart +# In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 200 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 200, 500, 100, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 200, 500, 100, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +disconnect con1; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +200 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +199 Clone Test Row - 199 umn Data Large Column Data Large +198 Clone Test Row - 198 umn Data Large Column Data Large +197 Clone Test Row - 197 umn Data Large Column Data Large +196 Clone Test Row - 196 umn Data Large Column Data Large +195 Clone Test Row - 195 umn Data Large Column Data Large +194 Clone Test Row - 194 umn Data Large Column Data Large +193 Clone Test Row - 193 umn Data Large Column Data Large +192 Clone Test Row - 192 umn Data Large Column Data Large +191 Clone Test Row - 191 umn Data Large Column Data Large +190 Clone Test Row - 190 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +200 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +199 Clone Test Row - 199 umn Data Large Column Data Large +198 Clone Test Row - 198 umn Data Large Column Data Large +197 Clone Test Row - 197 umn Data Large Column Data Large +196 Clone Test Row - 196 umn Data Large Column Data Large +195 Clone Test Row - 195 umn Data Large Column Data Large +194 Clone Test Row - 194 umn Data Large Column Data Large +193 Clone Test Row - 193 umn Data Large Column Data Large +192 Clone Test Row - 192 umn Data Large Column Data Large +191 Clone Test Row - 191 umn Data Large Column Data Large +190 Clone Test Row - 190 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 200, 200, 100, 0); +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +199 1990 Clone Test Row - 199 umn Data Large Column Data Large +198 1980 Clone Test Row - 198 umn Data Large Column Data Large +197 1970 Clone Test Row - 197 umn Data Large Column Data Large +196 1960 Clone Test Row - 196 umn Data Large Column Data Large +195 1950 Clone Test Row - 195 umn Data Large Column Data Large +194 1940 Clone Test Row - 194 umn Data Large Column Data Large +193 1930 Clone Test Row - 193 umn Data Large Column Data Large +192 1920 Clone Test Row - 192 umn Data Large Column Data Large +191 1910 Clone Test Row - 191 umn Data Large Column Data Large +190 1900 Clone Test Row - 190 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +199 1990 Clone Test Row - 199 umn Data Large Column Data Large +198 1980 Clone Test Row - 198 umn Data Large Column Data Large +197 1970 Clone Test Row - 197 umn Data Large Column Data Large +196 1960 Clone Test Row - 196 umn Data Large Column Data Large +195 1950 Clone Test Row - 195 umn Data Large Column Data Large +194 1940 Clone Test Row - 194 umn Data Large Column Data Large +193 1930 Clone Test Row - 193 umn Data Large Column Data Large +192 1920 Clone Test Row - 192 umn Data Large Column Data Large +191 1910 Clone Test Row - 191 umn Data Large Column Data Large +190 1900 Clone Test Row - 190 umn Data Large Column Data Large +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/local_file_extend.result b/mysql-test/suite/clone/r/local_file_extend.result new file mode 100644 index 0000000000000..d1e239c8afa8d --- /dev/null +++ b/mysql-test/suite/clone/r/local_file_extend.result @@ -0,0 +1,450 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 20, 20, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +20 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +19 190 Clone Test Row - 19 umn Data Large Column Data Large +18 180 Clone Test Row - 18 umn Data Large Column Data Large +17 170 Clone Test Row - 17 umn Data Large Column Data Large +16 160 Clone Test Row - 16 umn Data Large Column Data Large +15 150 Clone Test Row - 15 umn Data Large Column Data Large +14 140 Clone Test Row - 14 umn Data Large Column Data Large +13 130 Clone Test Row - 13 umn Data Large Column Data Large +12 120 Clone Test Row - 12 umn Data Large Column Data Large +11 110 Clone Test Row - 11 umn Data Large Column Data Large +10 100 Clone Test Row - 10 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +20 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +19 190 Clone Test Row - 19 umn Data Large Column Data Large +18 180 Clone Test Row - 18 umn Data Large Column Data Large +17 170 Clone Test Row - 17 umn Data Large Column Data Large +16 160 Clone Test Row - 16 umn Data Large Column Data Large +15 150 Clone Test Row - 15 umn Data Large Column Data Large +14 140 Clone Test Row - 14 umn Data Large Column Data Large +13 130 Clone Test Row - 13 umn Data Large Column Data Large +12 120 Clone Test Row - 12 umn Data Large Column Data Large +11 110 Clone Test Row - 11 umn Data Large Column Data Large +10 100 Clone Test Row - 10 umn Data Large Column Data Large +# In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_insert1 WAIT_FOR resume_clone1'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Insert [20 Rows - No commit] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_insert1'; +START TRANSACTION; +SELECT FILE_SIZE into @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +call execute_dml(0, 50, 20, 20, 500, 0); +SELECT FILE_SIZE > @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +FILE_SIZE > @t1_file_size +1 +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +connection default; +# In connection default - Cloning database +# In connection con1 +connection con1; +ROLLBACK; +connection default; +# In connection default - Cloning database +disconnect con1; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +40 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +69 Clone Test Row - 69 umn Data Large Column Data Large +68 Clone Test Row - 68 umn Data Large Column Data Large +67 Clone Test Row - 67 umn Data Large Column Data Large +66 Clone Test Row - 66 umn Data Large Column Data Large +65 Clone Test Row - 65 umn Data Large Column Data Large +64 Clone Test Row - 64 umn Data Large Column Data Large +63 Clone Test Row - 63 umn Data Large Column Data Large +62 Clone Test Row - 62 umn Data Large Column Data Large +61 Clone Test Row - 61 umn Data Large Column Data Large +60 Clone Test Row - 60 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +40 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +69 Clone Test Row - 69 umn Data Large Column Data Large +68 Clone Test Row - 68 umn Data Large Column Data Large +67 Clone Test Row - 67 umn Data Large Column Data Large +66 Clone Test Row - 66 umn Data Large Column Data Large +65 Clone Test Row - 65 umn Data Large Column Data Large +64 Clone Test Row - 64 umn Data Large Column Data Large +63 Clone Test Row - 63 umn Data Large Column Data Large +62 Clone Test Row - 62 umn Data Large Column Data Large +61 Clone Test Row - 61 umn Data Large Column Data Large +60 Clone Test Row - 60 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 10, 10, 2, 0); +commit; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +9 90 Clone Test Row - 9 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +0 0 Clone Test Row - 0 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +9 90 Clone Test Row - 9 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +0 0 Clone Test Row - 0 umn Data Large Column Data Large +# restart +# In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml WAIT_FOR resume_clone2'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_insert2 WAIT_FOR resume_clone3'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Insert [20 Rows - No commit] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml'; +START TRANSACTION; +SELECT FILE_SIZE into @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +call execute_dml(1, 0, 20, 20, 10, 1); +SELECT FILE_SIZE > @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +FILE_SIZE > @t1_file_size +0 +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_insert2'; +START TRANSACTION; +call execute_dml(0, 50, 20, 20, 500, 0); +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone3'; +connection default; +# In connection default - Cloning database +# In connection con1 +connection con1; +ROLLBACK; +connection default; +# In connection default - Cloning database +disconnect con1; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +40 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +69 Clone Test Row - 69 umn Data Large Column Data Large +68 Clone Test Row - 68 umn Data Large Column Data Large +67 Clone Test Row - 67 umn Data Large Column Data Large +66 Clone Test Row - 66 umn Data Large Column Data Large +65 Clone Test Row - 65 umn Data Large Column Data Large +64 Clone Test Row - 64 umn Data Large Column Data Large +63 Clone Test Row - 63 umn Data Large Column Data Large +62 Clone Test Row - 62 umn Data Large Column Data Large +61 Clone Test Row - 61 umn Data Large Column Data Large +60 Clone Test Row - 60 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +40 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +69 Clone Test Row - 69 umn Data Large Column Data Large +68 Clone Test Row - 68 umn Data Large Column Data Large +67 Clone Test Row - 67 umn Data Large Column Data Large +66 Clone Test Row - 66 umn Data Large Column Data Large +65 Clone Test Row - 65 umn Data Large Column Data Large +64 Clone Test Row - 64 umn Data Large Column Data Large +63 Clone Test Row - 63 umn Data Large Column Data Large +62 Clone Test Row - 62 umn Data Large Column Data Large +61 Clone Test Row - 61 umn Data Large Column Data Large +60 Clone Test Row - 60 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 10, 10, 2, 0); +commit; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +9 90 Clone Test Row - 9 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +0 0 Clone Test Row - 0 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +9 90 Clone Test Row - 9 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +0 0 Clone Test Row - 0 umn Data Large Column Data Large +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/local_insert.result b/mysql-test/suite/clone/r/local_insert.result new file mode 100644 index 0000000000000..0f09a1a90b966 --- /dev/null +++ b/mysql-test/suite/clone/r/local_insert.result @@ -0,0 +1,60 @@ +connect con1,localhost,root,,; +connect con2,localhost,root,,; +connect con3,localhost,root,,; +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE PROCEDURE prepare_data(IN val INT) +BEGIN +DECLARE i INT DEFAULT 1; +WHILE i <= val DO +INSERT INTO t1 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); +INSERT INTO t2 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); +SET i = i + 1; +END WHILE; +END| +# Case 1 - Normal page archiving process using clone client. +CREATE TABLE t1 (a INT AUTO_INCREMENT, b LONGBLOB, c LONGBLOB, key k1(a))ENGINE=InnoDB; +CREATE TABLE t2 (a INT PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +connection con1; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL page_signal WAIT_FOR go_page'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL redo_signal WAIT_FOR go_redo'; +SET DEBUG_SYNC = 'clone_donor_after_saving_dynamic_metadata SIGNAL meta_signal WAIT_FOR go_meta'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection con2; +SET DEBUG_SYNC = 'now WAIT_FOR page_signal'; +CALL prepare_data(50); +SET DEBUG_SYNC = 'now SIGNAL go_page'; +SET DEBUG_SYNC = 'now WAIT_FOR redo_signal'; +CALL prepare_data(10); +SET DEBUG_SYNC = 'now SIGNAL go_redo'; +SET DEBUG_SYNC = 'now WAIT_FOR meta_signal'; +CALL prepare_data(10); +UPDATE t1 SET a = 100 where a = 1; +UPDATE t2 SET a = 200 where a = 1; +SET DEBUG_SYNC = 'now SIGNAL go_meta'; +connection con1; +select count(*), max(a) from t1; +count(*) max(a) +70 100 +select count(*), max(a) from t2; +count(*) max(a) +70 200 +# Restart server on cloned data directory +# restart: with restart_parameters +select count(*), max(a) from t1; +count(*) max(a) +70 100 +select count(*), max(a) from t2; +count(*) max(a) +70 200 +INSERT INTO t1 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); +INSERT INTO t2 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); +# restart +connection con1; +DROP TABLE t1; +DROP TABLE t2; +SET DEBUG_SYNC = 'RESET'; +DROP PROCEDURE prepare_data; +UNINSTALL PLUGIN clone; +disconnect con1; +disconnect con2; +disconnect con3; diff --git a/mysql-test/suite/clone/r/local_partition.result b/mysql-test/suite/clone/r/local_partition.result new file mode 100644 index 0000000000000..d19de2113db3c --- /dev/null +++ b/mysql-test/suite/clone/r/local_partition.result @@ -0,0 +1,93 @@ +# DDL churn + partitions in InnoDB and MyISAM +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS +FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +DROP TABLE IF EXISTS t1, t2, p, isam_t1, isam_t2, isam_p; +Warnings: +Note 1051 Unknown table 'test.t1,test.t2,test.p,test.isam_t1,test.isam_t2,test.isam_p' +CREATE TABLE t1(a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); +CREATE TABLE t2(a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (4),(5),(6); +CREATE TABLE p(a INT) +ENGINE=InnoDB +PARTITION BY RANGE (a) +( +PARTITION p0 VALUES LESS THAN (100), +PARTITION p1 VALUES LESS THAN (200), +PARTITION p2 VALUES LESS THAN (300), +PARTITION p3 VALUES LESS THAN (400) +); +INSERT INTO p VALUES (1),(101),(201),(301); +CREATE TABLE isam_t1(a INT) ENGINE=MyISAM; +INSERT INTO isam_t1 VALUES (1),(2),(3); +CREATE TABLE isam_t2(a INT) ENGINE=MyISAM; +INSERT INTO isam_t2 VALUES (4),(5),(6); +CREATE TABLE isam_p(a INT) +ENGINE=MyISAM +PARTITION BY RANGE (a) +( +PARTITION p0 VALUES LESS THAN (100), +PARTITION p1 VALUES LESS THAN (200), +PARTITION p2 VALUES LESS THAN (300), +PARTITION p3 VALUES LESS THAN (400) +); +INSERT INTO isam_p VALUES (1),(101),(201),(301); +DROP TABLE t1; +DROP TABLE t2; +CREATE TABLE t2(a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (40),(50),(60); +ALTER TABLE p DROP PARTITION p0; +ALTER TABLE p DROP PARTITION p1; +ALTER TABLE p ADD PARTITION (PARTITION p4 VALUES LESS THAN (500)); +ALTER TABLE p ADD PARTITION (PARTITION p5 VALUES LESS THAN (600)); +INSERT INTO p VALUES (401),(501); +DROP TABLE isam_t1; +DROP TABLE isam_t2; +CREATE TABLE isam_t2(a INT) ENGINE=MyISAM; +INSERT INTO isam_t2 VALUES (40),(50),(60); +ALTER TABLE isam_p DROP PARTITION p0; +ALTER TABLE isam_p DROP PARTITION p1; +ALTER TABLE isam_p ADD PARTITION (PARTITION p4 VALUES LESS THAN (500)); +ALTER TABLE isam_p ADD PARTITION (PARTITION p5 VALUES LESS THAN (600)); +INSERT INTO isam_p VALUES (401),(501); +connection clone_conn_1; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection default; +# Restart server on cloned data directory +# restart: with restart_parameters +SELECT * FROM t1; +ERROR 42S02: Table 'test.t1' doesn't exist +SELECT * FROM t2 ORDER BY a; +a +40 +50 +60 +SELECT * FROM p ORDER BY a; +a +201 +301 +401 +501 +SELECT * FROM isam_t1; +ERROR 42S02: Table 'test.isam_t1' doesn't exist +SELECT * FROM isam_t2 ORDER BY a; +a +40 +50 +60 +SELECT * FROM isam_p ORDER BY a; +a +201 +301 +401 +501 +# restart +connection default; +DROP TABLE IF EXISTS p, t2, isam_p, isam_t2; +disconnect clone_conn_1; +connection default; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_partition_notwin.result b/mysql-test/suite/clone/r/local_partition_notwin.result new file mode 100644 index 0000000000000..1f20d523e3b2b --- /dev/null +++ b/mysql-test/suite/clone/r/local_partition_notwin.result @@ -0,0 +1,39 @@ +# MyISAM table with 400 partitions +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS +FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +DROP TABLE IF EXISTS t1; +Warnings: +Note 1051 Unknown table 'test.t1' +CREATE TABLE t1 ( +id BIGINT NOT NULL AUTO_INCREMENT, +PRIMARY KEY (id) +) ENGINE=MyISAM +PARTITION BY HASH (id) +PARTITIONS 400; +insert into t1 values (1),(2),(3),(4); +# clone Begins +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection default; +# Restart server on cloned data directory +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY HASH (`id`) +PARTITIONS 400 +SELECT COUNT(*) FROM t1; +COUNT(*) +4 +# restart +connection default; +DROP TABLE t1; +UNINSTALL PLUGIN clone; +disconnect clone_conn_1; +connection default; diff --git a/mysql-test/suite/clone/r/local_stage.result b/mysql-test/suite/clone/r/local_stage.result new file mode 100644 index 0000000000000..ddd6db02be88b --- /dev/null +++ b/mysql-test/suite/clone/r/local_stage.result @@ -0,0 +1,287 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 100, 100, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +# In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 100 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +disconnect con1; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +99 Clone Test Row - 99 umn Data Large Column Data Large +98 Clone Test Row - 98 umn Data Large Column Data Large +97 Clone Test Row - 97 umn Data Large Column Data Large +96 Clone Test Row - 96 umn Data Large Column Data Large +95 Clone Test Row - 95 umn Data Large Column Data Large +94 Clone Test Row - 94 umn Data Large Column Data Large +93 Clone Test Row - 93 umn Data Large Column Data Large +92 Clone Test Row - 92 umn Data Large Column Data Large +91 Clone Test Row - 91 umn Data Large Column Data Large +90 Clone Test Row - 90 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +99 Clone Test Row - 99 umn Data Large Column Data Large +98 Clone Test Row - 98 umn Data Large Column Data Large +97 Clone Test Row - 97 umn Data Large Column Data Large +96 Clone Test Row - 96 umn Data Large Column Data Large +95 Clone Test Row - 95 umn Data Large Column Data Large +94 Clone Test Row - 94 umn Data Large Column Data Large +93 Clone Test Row - 93 umn Data Large Column Data Large +92 Clone Test Row - 92 umn Data Large Column Data Large +91 Clone Test Row - 91 umn Data Large Column Data Large +90 Clone Test Row - 90 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/local_stage_sys.result b/mysql-test/suite/clone/r/local_stage_sys.result new file mode 100644 index 0000000000000..ddd6db02be88b --- /dev/null +++ b/mysql-test/suite/clone/r/local_stage_sys.result @@ -0,0 +1,287 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 100, 100, 10, 0); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +# In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Running Update Random [0 - 100 Key Range] +connect con1,localhost,root,,; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +# Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +connection default; +# In connection default - Cloning database +disconnect con1; +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +100 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +99 Clone Test Row - 99 umn Data Large Column Data Large +98 Clone Test Row - 98 umn Data Large Column Data Large +97 Clone Test Row - 97 umn Data Large Column Data Large +96 Clone Test Row - 96 umn Data Large Column Data Large +95 Clone Test Row - 95 umn Data Large Column Data Large +94 Clone Test Row - 94 umn Data Large Column Data Large +93 Clone Test Row - 93 umn Data Large Column Data Large +92 Clone Test Row - 92 umn Data Large Column Data Large +91 Clone Test Row - 91 umn Data Large Column Data Large +90 Clone Test Row - 90 umn Data Large Column Data Large +SHOW CREATE TABLE t2; +Table Create Table +t2 CREATE TABLE `t2` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci + PARTITION BY KEY (`col1`) +PARTITIONS 5 +SELECT count(*) from t2; +count(*) +100 +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +0 Clone Test Row - 0 umn Data Large Column Data Large +1 Clone Test Row - 1 umn Data Large Column Data Large +2 Clone Test Row - 2 umn Data Large Column Data Large +3 Clone Test Row - 3 umn Data Large Column Data Large +4 Clone Test Row - 4 umn Data Large Column Data Large +5 Clone Test Row - 5 umn Data Large Column Data Large +6 Clone Test Row - 6 umn Data Large Column Data Large +7 Clone Test Row - 7 umn Data Large Column Data Large +8 Clone Test Row - 8 umn Data Large Column Data Large +9 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col3 SUBSTRING(col4, 1000, 32) +99 Clone Test Row - 99 umn Data Large Column Data Large +98 Clone Test Row - 98 umn Data Large Column Data Large +97 Clone Test Row - 97 umn Data Large Column Data Large +96 Clone Test Row - 96 umn Data Large Column Data Large +95 Clone Test Row - 95 umn Data Large Column Data Large +94 Clone Test Row - 94 umn Data Large Column Data Large +93 Clone Test Row - 93 umn Data Large Column Data Large +92 Clone Test Row - 92 umn Data Large Column Data Large +91 Clone Test Row - 91 umn Data Large Column Data Large +90 Clone Test Row - 90 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +99 990 Clone Test Row - 99 umn Data Large Column Data Large +98 980 Clone Test Row - 98 umn Data Large Column Data Large +97 970 Clone Test Row - 97 umn Data Large Column Data Large +96 960 Clone Test Row - 96 umn Data Large Column Data Large +95 950 Clone Test Row - 95 umn Data Large Column Data Large +94 940 Clone Test Row - 94 umn Data Large Column Data Large +93 930 Clone Test Row - 93 umn Data Large Column Data Large +92 920 Clone Test Row - 92 umn Data Large Column Data Large +91 910 Clone Test Row - 91 umn Data Large Column Data Large +90 900 Clone Test Row - 90 umn Data Large Column Data Large +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/local_vector.result b/mysql-test/suite/clone/r/local_vector.result new file mode 100644 index 0000000000000..65c12b07be3d4 --- /dev/null +++ b/mysql-test/suite/clone/r/local_vector.result @@ -0,0 +1,82 @@ +# Clone test for VECTOR data type and indexes (InnoDB + MyISAM) +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SELECT PLUGIN_NAME, PLUGIN_STATUS +FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; +PLUGIN_NAME PLUGIN_STATUS +clone ACTIVE +DROP TABLE IF EXISTS t_innodb, t_myisam; +Warnings: +Note 1051 Unknown table 'test.t_innodb,test.t_myisam' +CREATE TABLE t_innodb ( +id INT AUTO_INCREMENT PRIMARY KEY, +v VECTOR(5) NOT NULL, +VECTOR INDEX (v) +) ENGINE=InnoDB; +INSERT INTO t_innodb (v) VALUES +(Vec_FromText('[0.418,0.809,0.823,0.598,0.033]')), +(Vec_FromText('[0.687,0.789,0.496,0.574,0.917]')), +(Vec_FromText('[0.333,0.962,0.467,0.448,0.475]')); +CREATE TABLE t_myisam ( +a INT, +v VECTOR(1) NOT NULL, +VECTOR(v) +) ENGINE=MyISAM; +INSERT INTO t_myisam VALUES +(1, 0x31313131), +(2, 0x32323232); +SELECT * FROM t_innodb +ORDER BY vec_distance_euclidean(v, Vec_FromText('[1,0,0,0,0]')) +LIMIT 1; +id v +3 �~�>�Ev?��>B`�>33�> +SELECT * FROM t_myisam +ORDER BY vec_distance_euclidean(v, 0x30303030) +LIMIT 1; +a v +1 1111 +connection clone_conn_1; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# Restart server on cloned data directory +connection default; +# restart: with restart_parameters +SHOW CREATE TABLE t_innodb; +Table Create Table +t_innodb CREATE TABLE `t_innodb` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `v` vector(5) NOT NULL, + PRIMARY KEY (`id`), + VECTOR KEY `v` (`v`) +) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SHOW CREATE TABLE t_myisam; +Table Create Table +t_myisam CREATE TABLE `t_myisam` ( + `a` int(11) DEFAULT NULL, + `v` vector(1) NOT NULL, + VECTOR KEY `v` (`v`) +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT id, Vec_ToText(v) FROM t_innodb; +id Vec_ToText(v) +1 [0.418,0.809,0.823,0.598,0.033] +2 [0.687,0.789,0.496,0.574,0.917] +3 [0.333,0.962,0.467,0.448,0.475] +SELECT a, Vec_ToText(v) FROM t_myisam; +a Vec_ToText(v) +1 [2.57849e-9] +2 [1.03724e-8] +SELECT id FROM t_innodb +ORDER BY vec_distance_euclidean(v, Vec_FromText('[1,0,0,0,0]')) +LIMIT 1; +id +3 +SELECT * FROM t_myisam +ORDER BY vec_distance_euclidean(v, 0x30303030) +LIMIT 1; +a v +1 1111 +# restart +connection default; +DROP TABLE t_innodb, t_myisam; +disconnect clone_conn_1; +connection default; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/r/local_xa.result b/mysql-test/suite/clone/r/local_xa.result new file mode 100644 index 0000000000000..37bcfc83e1282 --- /dev/null +++ b/mysql-test/suite/clone/r/local_xa.result @@ -0,0 +1,155 @@ +call mtr.add_suppression("\\[Warning\\] Found 1 prepared XA transactions"); +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB; +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB +PARTITION BY KEY(col1) PARTITIONS 5; +CREATE PROCEDURE execute_dml( +p_dml_type INT, +p_key_min INT, +p_key_range INT, +p_loop_count INT, +p_frequency INT, +p_is_rand INT) +BEGIN +DECLARE v_idx INT DEFAULT 0; +DECLARE v_commit INT DEFAULT 0; +DECLARE v_key INT DEFAULT 0; +/* Loop and INSERT data at random position */ +WHILE(v_idx < p_loop_count) DO +/* Generate key between 1 to p_loop_count */ +IF p_is_rand = 1 THEN +SET v_key = p_key_min + FLOOR(RAND() * p_key_range); +ELSE +SET v_key = p_key_min + (v_idx % p_key_range); +END IF; +CASE p_dml_type +WHEN 0 THEN +SET @clol3_text = CONCAT('Clone Test Row - ', v_key); +INSERT INTO t1 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +INSERT INTO t2 (col1, col2, col3, col4) VALUES ( +v_key, v_key * 10, +@clol3_text, REPEAT('Large Column Data ', 2048)) +ON DUPLICATE KEY UPDATE col2 = col2 + 1; +WHEN 1 THEN +UPDATE t1 SET col2 = v_idx + 1 WHERE col1 = v_key; +UPDATE t2 SET col2 = v_idx + 1 WHERE col1 = v_key; +WHEN 2 THEN +DELETE FROM t1 WHERE col1 = v_key; +DELETE FROM t2 WHERE col1 = v_key; +ELSE +DELETE FROM t1; +DELETE FROM t2; +END CASE; +SET v_idx = v_idx + 1; +/* Commit or rollback work at specified frequency. */ +IF v_idx % p_frequency = 0 THEN +SET v_commit = FLOOR(RAND() * 2); +IF v_commit = 0 AND p_is_rand = 1 THEN +ROLLBACK; +START TRANSACTION; +ELSE +COMMIT; +START TRANSACTION; +END IF; +END IF; +END WHILE; +COMMIT; +END| +call execute_dml(0, 0, 10, 10, 1, 0); +commit; +## Test: Clone with XA transactions +XA start 'xa_trx_1'; +update t1 set col2 = 100; +XA end 'xa_trx_1'; +XA prepare 'xa_trx_1'; +connect con1,localhost,root,,,; +# In connection default - Start Cloning database +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 - Finish XA prepare, Start XA commit +connection default; +XA commit 'xa_trx_1'; +# In connection default +disconnect con1; +SELECT count(*) from t1; +count(*) +10 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 100 Clone Test Row - 0 umn Data Large Column Data Large +1 100 Clone Test Row - 1 umn Data Large Column Data Large +2 100 Clone Test Row - 2 umn Data Large Column Data Large +3 100 Clone Test Row - 3 umn Data Large Column Data Large +4 100 Clone Test Row - 4 umn Data Large Column Data Large +5 100 Clone Test Row - 5 umn Data Large Column Data Large +6 100 Clone Test Row - 6 umn Data Large Column Data Large +7 100 Clone Test Row - 7 umn Data Large Column Data Large +8 100 Clone Test Row - 8 umn Data Large Column Data Large +9 100 Clone Test Row - 9 umn Data Large Column Data Large +# Restart cloned database +# restart: with restart_parameters +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `col1` int(11) NOT NULL, + `col2` int(11) DEFAULT NULL, + `col3` varchar(64) DEFAULT NULL, + `col4` blob DEFAULT NULL, + PRIMARY KEY (`col1`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci +SELECT count(*) from t1; +count(*) +10 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +XA recover; +formatID gtrid_length bqual_length data +1 8 0 xa_trx_1 +XA commit 'xa_trx_1'; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 100 Clone Test Row - 0 umn Data Large Column Data Large +1 100 Clone Test Row - 1 umn Data Large Column Data Large +2 100 Clone Test Row - 2 umn Data Large Column Data Large +3 100 Clone Test Row - 3 umn Data Large Column Data Large +4 100 Clone Test Row - 4 umn Data Large Column Data Large +5 100 Clone Test Row - 5 umn Data Large Column Data Large +6 100 Clone Test Row - 6 umn Data Large Column Data Large +7 100 Clone Test Row - 7 umn Data Large Column Data Large +8 100 Clone Test Row - 8 umn Data Large Column Data Large +9 100 Clone Test Row - 9 umn Data Large Column Data Large +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 10, 10, 1, 0); +SELECT count(*) from t1; +count(*) +10 +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; +col1 col2 col3 SUBSTRING(col4, 1000, 32) +0 0 Clone Test Row - 0 umn Data Large Column Data Large +1 10 Clone Test Row - 1 umn Data Large Column Data Large +2 20 Clone Test Row - 2 umn Data Large Column Data Large +3 30 Clone Test Row - 3 umn Data Large Column Data Large +4 40 Clone Test Row - 4 umn Data Large Column Data Large +5 50 Clone Test Row - 5 umn Data Large Column Data Large +6 60 Clone Test Row - 6 umn Data Large Column Data Large +7 70 Clone Test Row - 7 umn Data Large Column Data Large +8 80 Clone Test Row - 8 umn Data Large Column Data Large +9 90 Clone Test Row - 9 umn Data Large Column Data Large +# restart +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; diff --git a/mysql-test/suite/clone/r/monitor_progress.result b/mysql-test/suite/clone/r/monitor_progress.result new file mode 100644 index 0000000000000..343a03512e21a --- /dev/null +++ b/mysql-test/suite/clone/r/monitor_progress.result @@ -0,0 +1,228 @@ +connect con1,localhost,root,,; +CALL sys.ps_setup_disable_thread(CONNECTION_ID()); +summary +Disabled 1 thread +connect con2,localhost,root,,; +CALL sys.ps_setup_disable_thread(CONNECTION_ID()); +summary +Disabled 1 thread +connect con3,localhost,root,,; +CALL sys.ps_setup_disable_thread(CONNECTION_ID()); +summary +Disabled 1 thread +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +CALL sys.ps_setup_enable_instrument('%stage/innodb/clone%'); +summary +Enabled X instruments +CALL sys.ps_setup_enable_instrument('statement/clone/%'); +summary +Enabled X instruments +CALL sys.ps_setup_enable_consumer('events_statements%'); +summary +Enabled X consumers +CALL sys.ps_setup_enable_consumer('events_stages%'); +summary +Enabled X consumers +SELECT * +FROM performance_schema.setup_instruments +WHERE name LIKE "%stage/innodb/clone%" +OR name LIKE "statement/clone/%" +OR name LIKE "wait/io/file/innodb/innodb_clone_file" +ORDER BY NAME; +NAME ENABLED TIMED +stage/innodb/clone (file copy) YES YES +stage/innodb/clone (page copy) YES YES +stage/innodb/clone (redo copy) YES YES +statement/clone/local YES YES +wait/io/file/innodb/innodb_clone_file YES YES +SELECT * +FROM performance_schema.setup_consumers +WHERE name LIKE "events_statements_%" OR name LIKE "events_stages_%" +ORDER BY NAME; +NAME ENABLED +events_stages_current YES +events_stages_history YES +events_stages_history_long YES +events_statements_current YES +events_statements_history YES +events_statements_history_long YES +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; +# Case 1 - Monitoring a normal Clone operation. +connection con1; +CALL sys.ps_setup_enable_thread(CONNECTION_ID()); +summary +Enabled 1 thread +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +SELECT EVENT_NAME, TIMER_START > 0, TIMER_END > 0, TIMER_WAIT > 0, +SQL_TEXT, CURRENT_SCHEMA +FROM performance_schema.events_statements_history_long +WHERE event_name LIKE "statement/clone/%" +ORDER BY EVENT_NAME; +EVENT_NAME TIMER_START > 0 TIMER_END > 0 TIMER_WAIT > 0 SQL_TEXT CURRENT_SCHEMA +statement/clone/local 1 1 1 CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' test +SELECT EVENT_NAME, TIMER_START > 0, +TIMER_END > 0, WORK_COMPLETED = WORK_ESTIMATED +FROM performance_schema.events_stages_history_long +WHERE event_name LIKE "%stage/innodb/clone%" +ORDER BY EVENT_NAME; +EVENT_NAME TIMER_START > 0 TIMER_END > 0 WORK_COMPLETED = WORK_ESTIMATED +stage/innodb/clone (file copy) 1 1 1 +stage/innodb/clone (page copy) 1 1 1 +stage/innodb/clone (redo copy) 1 1 1 +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; +# Case 2 - Monitoring Clone operation which has more estimated work +# during file and page copy stage than in a default run. +connection con1; +CREATE PROCEDURE prepare_data(IN val INT) +BEGIN +DECLARE i INT DEFAULT 0; +WHILE i < val DO +INSERT INTO t1 (b,c) VALUES (REPEAT(a,500), REPEAT(b,100)); +INSERT INTO t2 (b,c) VALUES (REPEAT(a,500), REPEAT(b,100)); +INSERT INTO t3 (b,c) VALUES (REPEAT(a,500), REPEAT(b,100)); +SET i = i + 1; +END WHILE; +END| +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL page_signal WAIT_FOR go_page'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL redo_signal WAIT_FOR go_redo'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection con2; +SET DEBUG_SYNC = 'now WAIT_FOR page_signal'; +CALL prepare_data(50); +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL go_page'; +connection con3; +SET DEBUG_SYNC = 'now WAIT_FOR redo_signal'; +SELECT EVENT_NAME, TIMER_START > 0, TIMER_END > 0, TIMER_WAIT > 0, +SQL_TEXT, CURRENT_SCHEMA +FROM performance_schema.events_statements_current +WHERE event_name LIKE "statement/clone/%" +ORDER BY EVENT_NAME; +EVENT_NAME TIMER_START > 0 TIMER_END > 0 TIMER_WAIT > 0 SQL_TEXT CURRENT_SCHEMA +statement/clone/local 1 1 1 CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' test +CALL prepare_data(50); +SET DEBUG_SYNC = 'now SIGNAL go_redo'; +connection con1; +SELECT EVENT_NAME, WORK_COMPLETED > 0, TIMER_START > 0, +TIMER_END > 0, WORK_COMPLETED = WORK_ESTIMATED +FROM performance_schema.events_stages_history_long +WHERE event_name LIKE "%stage/innodb/clone%" +ORDER BY EVENT_NAME; +EVENT_NAME WORK_COMPLETED > 0 TIMER_START > 0 TIMER_END > 0 WORK_COMPLETED = WORK_ESTIMATED +stage/innodb/clone (file copy) 1 1 1 1 +stage/innodb/clone (page copy) 1 1 1 1 +stage/innodb/clone (redo copy) 1 1 1 1 +SELECT EVENT_NAME, TIMER_START > 0, TIMER_END > 0, TIMER_WAIT > 0, +SQL_TEXT, CURRENT_SCHEMA +FROM performance_schema.events_statements_history_long +WHERE event_name LIKE "statement/clone/%"; +EVENT_NAME TIMER_START > 0 TIMER_END > 0 TIMER_WAIT > 0 SQL_TEXT CURRENT_SCHEMA +statement/clone/local 1 1 1 CLONE LOCAL DATA DIRECTORY = '$CLONE_DATADIR' test +SET DEBUG_SYNC='RESET'; +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +# Case 3 - Monitoring progress in the middle of file copy. +connection con1; +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b INT); +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b INT); +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b INT); +SET DEBUG_SYNC = 'clone_file_copy SIGNAL file_signal WAIT_FOR go_file'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection con2; +SET DEBUG_SYNC= 'now WAIT_FOR file_signal'; +SELECT EVENT_NAME, WORK_COMPLETED <= WORK_ESTIMATED +FROM performance_schema.events_stages_current +WHERE event_name LIKE "%file copy%" +ORDER BY EVENT_NAME; +EVENT_NAME WORK_COMPLETED <= WORK_ESTIMATED +stage/innodb/clone (file copy) 1 +SET DEBUG_SYNC= 'now SIGNAL go_file'; +connection con1; +SET DEBUG_SYNC = 'RESET'; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; +# Case 4 - Monitoring progress in the middle of page copy. +connection con1; +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL page_signal WAIT_FOR go_page'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL page_middle_signal WAIT_FOR go_page_middle'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection con2; +SET DEBUG_SYNC = 'now WAIT_FOR page_signal'; +CALL prepare_data(50); +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL go_page'; +connection con3; +SET DEBUG_SYNC = 'now WAIT_FOR page_middle_signal'; +SELECT EVENT_NAME, WORK_COMPLETED <= WORK_ESTIMATED +FROM performance_schema.events_stages_current +WHERE event_name LIKE "%page copy%" +ORDER BY EVENT_NAME; +EVENT_NAME WORK_COMPLETED <= WORK_ESTIMATED +stage/innodb/clone (page copy) 1 +SET DEBUG_SYNC = 'now SIGNAL go_page_middle'; +connection con1; +SET DEBUG_SYNC = 'RESET'; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; +# Case 5 - Monitoring progress in the middle of redo copy. +connection con1; +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL redo_signal WAIT_FOR go_redo'; +SET DEBUG_SYNC = 'clone_redo_copy SIGNAL redo_middle_signal WAIT_FOR go_redo_middle'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +connection con2; +SET DEBUG_SYNC= 'now WAIT_FOR redo_signal'; +CALL prepare_data(50); +SET DEBUG_SYNC= 'now SIGNAL go_redo'; +connection con3; +SET DEBUG_SYNC = 'now WAIT_FOR redo_middle_signal'; +SELECT EVENT_NAME, WORK_COMPLETED <= WORK_ESTIMATED +FROM performance_schema.events_stages_current +WHERE event_name LIKE "%redo copy%" +ORDER BY EVENT_NAME; +EVENT_NAME WORK_COMPLETED <= WORK_ESTIMATED +stage/innodb/clone (redo copy) 1 +SET DEBUG_SYNC = 'now SIGNAL go_redo_middle'; +connection con1; +SET DEBUG_SYNC = 'RESET'; +DROP PROCEDURE prepare_data; +USE test; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; +connection default; +UNINSTALL PLUGIN clone; +disconnect con1; +disconnect con2; +disconnect con3; diff --git a/mysql-test/suite/clone/r/redo_log_resize.result b/mysql-test/suite/clone/r/redo_log_resize.result new file mode 100644 index 0000000000000..21c2b591f8079 --- /dev/null +++ b/mysql-test/suite/clone/r/redo_log_resize.result @@ -0,0 +1,25 @@ +INSTALL PLUGIN clone SONAME 'CLONE_PLUGIN'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +# In connection con1 +connect con1,localhost,root,,; +SET DEBUG_SYNC='now WAIT_FOR start_dml1'; +SET global innodb_log_file_size=4*1024*1024; +ERROR HY000: Concurrent clone in progress. Please try after clone is complete. +SET DEBUG_SYNC= 'now SIGNAL resume_clone1'; +connection default; +SET DEBUG_SYNC="redo_log_resizing SIGNAL clone_start WAIT_FOR redo_finish"; +SET global innodb_log_file_size=4*1024*1024; +connection con1; +set DEBUG_SYNC="now WAIT_FOR clone_start"; +CLONE LOCAL DATA DIRECTORY = 'CLONE_DATADIR'; +ERROR HY000: Concurrent DDL is performed during clone operation. Please try again. +set DEBUG_SYNC="now SIGNAL redo_finish"; +connection default; +disconnect con1; +DROP TABLE t1; +DROP TABLE t2; +DROP PROCEDURE execute_dml; +UNINSTALL PLUGIN clone; +SET GLOBAL innodb_log_file_size = 10 *1024*1024; +SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/suite/clone/t/aria_basic.opt b/mysql-test/suite/clone/t/aria_basic.opt new file mode 100644 index 0000000000000..423cae6133bea --- /dev/null +++ b/mysql-test/suite/clone/t/aria_basic.opt @@ -0,0 +1 @@ +--skip_partition=0 diff --git a/mysql-test/suite/clone/t/aria_basic.test b/mysql-test/suite/clone/t/aria_basic.test new file mode 100644 index 0000000000000..db6d1cc748812 --- /dev/null +++ b/mysql-test/suite/clone/t/aria_basic.test @@ -0,0 +1,215 @@ +--source include/have_aria.inc +--source include/not_embedded.inc +--source include/have_debug.inc +--source ../include/clone_connection_begin.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--echo ### +--echo # Test for mix of online/offline backup tables +--echo ##### + +CREATE TABLE t_default(i INT PRIMARY KEY) + ENGINE ARIA; +INSERT INTO t_default VALUES (1); + +CREATE TABLE t_tr_p_ch(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO t_tr_p_ch VALUES (1); + +CREATE TABLE t_tr_p_nch(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=0; +INSERT INTO t_tr_p_nch VALUES (1); + +CREATE TABLE t_p_ch(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO t_p_ch VALUES (1); + +CREATE TABLE t_p_nch(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=PAGE PAGE_CHECKSUM=0; +INSERT INTO t_p_nch VALUES (1); + +CREATE TABLE t_fixed(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=FIXED PAGE_CHECKSUM=1; +INSERT INTO t_fixed VALUES (1); + +CREATE TABLE t_dyn(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=0 ROW_FORMAT=DYNAMIC PAGE_CHECKSUM=1; +INSERT INTO t_dyn VALUES (1); + +--echo # Test for partitioned table +CREATE TABLE t_part_online(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL = 1 PAGE_CHECKSUM = 1 + PARTITION BY RANGE( i ) ( + PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20), + PARTITION p2 VALUES LESS THAN (30) + ); + +INSERT INTO t_part_online VALUES(5); +INSERT INTO t_part_online VALUES(15); +INSERT INTO t_part_online VALUES(25); +SELECT * FROM t_part_online; + +CREATE TABLE t_part_offline(i INT) + ENGINE ARIA TRANSACTIONAL = 0 PAGE_CHECKSUM = 0 + PARTITION BY RANGE( i ) ( + PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20), + PARTITION p2 VALUES LESS THAN (30) + ); + +INSERT INTO t_part_offline VALUES(5); +INSERT INTO t_part_offline VALUES(15); +INSERT INTO t_part_offline VALUES(25); + +--echo # Test for filename to tablename mapping +CREATE TABLE `t 1 t-1`(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO `t 1 t-1` VALUES (1); + +CREATE TABLE `t-part online`(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL = 1 PAGE_CHECKSUM = 1 + PARTITION BY RANGE( i ) ( + PARTITION p0 VALUES LESS THAN (10), + PARTITION p1 VALUES LESS THAN (20), + PARTITION p2 VALUES LESS THAN (30) + ); +INSERT INTO `t-part online` VALUES(5); +INSERT INTO `t-part online` VALUES(15); +INSERT INTO `t-part online` VALUES(25); + + +--echo ### +--echo # Test for redo log files backup; +--echo ##### +CREATE TABLE t_logs_1(i INT) + ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +CREATE TABLE t_logs_2 LIKE t_logs_1; +CREATE TABLE t_bulk_ins LIKE t_logs_1; +INSERT INTO t_logs_1 VALUES + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), + (0), (1), (2), (3), (4), (5), (6), (7), (8), (9); +--echo # Generate several log files +--let $i = 0 +while ($i < 14) { +INSERT INTO t_logs_1 SELECT * FROM t_logs_1; +--inc $i +} + +--echo ### +--echo # Test for DML during backup for online backup +--echo ##### + +CREATE TABLE t_dml(i INT PRIMARY KEY) + ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; +INSERT INTO t_dml VALUES(1), (2), (3); +SET SESSION debug_dbug="+d,maria_flush_whole_log"; +SET GLOBAL aria_checkpoint_interval=10000; + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +--connection clone_conn_1 +SET DEBUG_SYNC= 'after_aria_table_copy_t_dml SIGNAL dml_start WAIT_FOR aria_1'; +--source ../include/clone_command_send.inc + +connection default; +set DEBUG_SYNC="now WAIT_FOR dml_start"; +DELETE FROM test.t_dml where i = 3; +UPDATE test.t_dml SET i = 4 where i = 1; +INSERT INTO test.t_dml VALUES(5); +SELECT * FROM test.t_dml; +SET DEBUG_SYNC="now SIGNAL aria_1"; + +connection clone_conn_1; +reap; + +--let $t_logs_1_records_count_before_backup=`SELECT COUNT(*) FROM t_logs_1` +--let $t_logs_2_records_count_before_backup=`SELECT COUNT(*) FROM t_logs_2` +--let $t_bulk_ins_records_count_before_backup=`SELECT COUNT(*) FROM t_bulk_ins` + +--connection default +--echo # Restart server on cloned data directory +--let $restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + + +--echo ### Result for DML test +SELECT * FROM t_dml; + +--echo ### Result for redo log files backup +--let $t_logs_1_records_count_after_backup=`SELECT COUNT(*) FROM t_logs_1` +--let $t_logs_2_records_count_after_backup=`SELECT COUNT(*) FROM t_logs_2` +--let $t_bulk_ins_records_count_after_backup=`SELECT COUNT(*) FROM t_bulk_ins` +if ($t_logs_1_records_count_after_backup == $t_logs_1_records_count_before_backup) { +--echo # ok +} +if ($t_logs_1_records_count_after_backup != $t_logs_1_records_count_before_backup) { +--echo # failed +} +if ($t_logs_2_records_count_after_backup == $t_logs_2_records_count_before_backup) { +--echo # ok +} +if ($t_logs_2_records_count_after_backup != $t_logs_2_records_count_before_backup) { +--echo # failed +} +if ($t_bulk_ins_records_count_after_backup == $t_bulk_ins_records_count_before_backup) { +--echo # ok +} +if ($t_bulk_ins_records_count_after_backup != $t_bulk_ins_records_count_before_backup) { +--echo # failed +} + +--let restart_parameters= +--source include/restart_mysqld.inc + +--echo ### Clean up for DML test +DROP TABLE t_dml; +--echo ### Cleanup for redo log files backup +DROP TABLE t_logs_1; +DROP TABLE t_logs_2; +DROP TABLE t_bulk_ins; +--let $t_logs_1_records_count_before_backup= +--let $t_logs_1_records_count_after_backup= +--let $t_logs_2_records_count_before_backup= +--let $t_logs_2_records_count_after_backup= +--let $t_bulk_ins_records_count_before_backup= +--let $t_bulk_ins_records_count_after_backup= + +--echo ### Result for online/offline tables test +SELECT * FROM t_default; +SELECT * FROM t_tr_p_ch; +SELECT * FROM t_tr_p_nch; +SELECT * FROM t_p_ch; +SELECT * FROM t_p_nch; +SELECT * FROM t_fixed; +SELECT * FROM t_dyn; +SELECT * FROM t_part_online; +SELECT * FROM t_part_offline; +SELECT * FROM `t 1 t-1`; +SELECT * FROM `t-part online`; + +--echo ### Cleanup for online/offline tables test +DROP TABLE t_default; +DROP TABLE t_tr_p_ch; +DROP TABLE t_tr_p_nch; +DROP TABLE t_p_ch; +DROP TABLE t_p_nch; +DROP TABLE t_fixed; +DROP TABLE t_dyn; +DROP TABLE t_part_online; +DROP TABLE t_part_offline; +DROP TABLE `t 1 t-1`; +DROP TABLE `t-part online`; +--source ../include/clone_connection_end.inc +rmdir $CLONE_DATADIR; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/backup_stage_and_lock.test b/mysql-test/suite/clone/t/backup_stage_and_lock.test new file mode 100644 index 0000000000000..18fce9f30a629 --- /dev/null +++ b/mysql-test/suite/clone/t/backup_stage_and_lock.test @@ -0,0 +1,54 @@ +# Test clone with different table types with debug sync +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc +--source include/not_embedded.inc + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SET DEBUG_SYNC="backup_stage_start SIGNAL start_con1 WAIT_FOR res_clone1"; +--source ../include/clone_command_send.inc + +connect(con1,localhost,root,,,); +SET DEBUG_SYNC="now WAIT_FOR start_con1"; +SET lock_wait_timeout=1; +--error ER_LOCK_WAIT_TIMEOUT +BACKUP STAGE START; +SET DEBUG_SYNC="now SIGNAL res_clone1"; + +connection default; +reap; + +# Clone wait for backup stage +BACKUP STAGE START; +rmdir $CLONE_DATADIR; +SET lock_wait_timeout=1; +connection con1; +let $clone_err= ER_LOCK_WAIT_TIMEOUT; +--source ../include/clone_command.inc + +connection default; +rmdir $CLONE_DATADIR; +let $clone_err=0; +BACKUP STAGE END; +# backup lock wait for clone backup lock +SET DEBUG_SYNC="clone_backup_lock SIGNAL con1_wait WAIT_FOR res_clone2"; +--source ../include/clone_command_send.inc + +connection con1; +set DEBUG_SYNC="now WAIT_FOR con1_wait"; +# BACKUP LOCK should succeed +BACKUP LOCK mysql.table_stats; +BACKUP UNLOCK; +SET DEBUG_SYNC="now SIGNAL res_clone2"; + +connection default; +reap; +disconnect con1; + +UNINSTALL PLUGIN clone; +rmdir $CLONE_DATADIR; diff --git a/mysql-test/suite/clone/t/error_archival.test b/mysql-test/suite/clone/t/error_archival.test new file mode 100644 index 0000000000000..d77e4fe48d0c1 --- /dev/null +++ b/mysql-test/suite/clone/t/error_archival.test @@ -0,0 +1,161 @@ +# Test clone with debug sync point to simulate archiving error in different stage + +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc +--source include/not_embedded.inc + +call mtr.add_suppression("\\[ERROR\\] InnoDB: Log writer waited too long for archiver"); + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +--source ../include/create_schema.inc + +## Execute Clone while concurrent DMLs are in progress + +# Insert 100 rows +call execute_dml(0, 0, 100, 100, 10, 0); + +# Check base rows +SHOW CREATE TABLE t1; + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +--echo # Test-1: Error during redo archival +--echo # In connection default - Cloning database +SET GLOBAL DEBUG = '+d,clone_redo_archive_error'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 100 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--error ER_INTERNAL_ERROR +--reap +--rmdir $CLONE_DATADIR +SET GLOBAL DEBUG = '-d,clone_redo_archive_error'; +SET DEBUG_SYNC = 'RESET'; + +--echo # Test-2: Error overwrite redo archival data +--echo # In connection default - Cloning database +SET GLOBAL DEBUG = '+d,clone_redo_no_archive'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Delete all rows +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +call execute_dml(3, 0, 1, 1, 1, 0); +COMMIT; +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +--echo # In connection con1 - Insert 200 rows +call execute_dml(0, 0, 200, 200, 10, 0); +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--error ER_INTERNAL_ERROR +--reap +--rmdir $CLONE_DATADIR +SET GLOBAL DEBUG = '-d,clone_redo_no_archive'; +SET DEBUG_SYNC = 'RESET'; + +--echo # Test-3: Successful clone after archival error +--echo # In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 100 Key Range] +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--reap +SET DEBUG_SYNC = 'RESET'; + +disconnect con1; +--echo # Restart server on cloned data directory +--let $restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +#Cleanup +--let restart_parameters= +--source include/restart_mysqld.inc + +--source ../include/drop_schema.inc + +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; + +--source include/wait_until_count_sessions.inc +--rmdir $CLONE_DATADIR diff --git a/mysql-test/suite/clone/t/error_basic.test b/mysql-test/suite/clone/t/error_basic.test new file mode 100644 index 0000000000000..3d903e66afa76 --- /dev/null +++ b/mysql-test/suite/clone/t/error_basic.test @@ -0,0 +1,87 @@ +# Test clone all error conditions +--source include/not_embedded.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR= `select @@datadir;` + +--echo # 1. PLUGIN not loaded - clone local +--let $clone_err = ER_PLUGIN_IS_NOT_LOADED +--source ../include/clone_command.inc +--let $clone_err = 0 + +--echo # 1A. PLUGIN not installed - Uninstall plugin +--error ER_SP_DOES_NOT_EXIST +UNINSTALL PLUGIN clone; + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +--echo # 1B. PLUGIN already loaded - Install plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--error ER_PLUGIN_INSTALLED +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +if($remote_clone) { +--echo # 1B-a. Incorrect PORT number for remote clone +--let $PORT =`select @@port + 1` +--let $clone_err = ER_CLONE_DONOR +--source ../include/clone_command.inc +--let $PORT =`select @@port` +--let $clone_err = 0 + +--echo # 1B-b. MAX_ALLOWED_PACKET size too low for remote clone +SET GLOBAL MAX_ALLOWED_PACKET = 512 * 1024; +--connect (con1,localhost,root,,) + +--let $clone_err = ER_CLONE_DONOR +--source ../include/clone_command.inc +--let $clone_err = 0 + +--connection default +--disconnect con1 + +--echo # 1B-c. MAX_ALLOWED_PACKET 2M should pass +SET GLOBAL MAX_ALLOWED_PACKET = 2 * 1024 * 1024; +--connect (con1,localhost,root,,) + +--source ../include/clone_command.inc + +--connection default +--disconnect con1 +--rmdir $CLONE_DATADIR + +SET GLOBAL MAX_ALLOWED_PACKET = DEFAULT; +} + +--echo #1C. Clone data without error +--source ../include/clone_command.inc + +--echo # 2A. Incorrect PATH - Relative path +--let $CLONE_DATADIR = ./data +--let $clone_err = ER_WRONG_VALUE +--source ../include/clone_command.inc +--let $clone_err = 0 +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--echo # 2B. Incorrect PATH - Too long +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new/a#######################################################b#######################################################################################b#####################################################################################b######################################################################################b######################################################################################b############################################################################################################# +--let $clone_err = ER_PATH_LENGTH +--source ../include/clone_command.inc +--let $clone_err = 0 +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--echo # 2C. Incorrect PATH - Within data directory +--let $CLONE_DATADIR = $MYSQLD_DATADIR/data +--let $clone_err = ER_PATH_IN_DATADIR +--source ../include/clone_command.inc +--let $clone_err = 0 +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--echo # 2D. Incorrect PATH - data directory exists +--let $clone_err = ER_DB_CREATE_EXISTS +--source ../include/clone_command.inc +--let $clone_err = 0 + +--echo #Cleanup +UNINSTALL PLUGIN clone; + +--rmdir $CLONE_DATADIR diff --git a/mysql-test/suite/clone/t/error_features.test b/mysql-test/suite/clone/t/error_features.test new file mode 100644 index 0000000000000..ea055ef64e587 --- /dev/null +++ b/mysql-test/suite/clone/t/error_features.test @@ -0,0 +1,49 @@ +# Test clone error conditions with incompatible features +--source include/not_embedded.inc +let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new; +let $MYSQLD_DATADIR= `select @@datadir`; + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +CREATE DATABASE testdb_clone; +CREATE USER 'user_clone'@'localhost' IDENTIFIED BY '123'; +GRANT ALL ON testdb_clone.* TO 'user_clone'@'localhost'; +GRANT SELECT ON performance_schema.* to 'user_clone'@'localhost'; +SHOW GRANTS FOR 'user_clone'@'localhost'; + +--echo # Connection without NECESSARY privilege +--connect (con1,'localhost','user_clone','123',) +SELECT user(); +USE testdb_clone; +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 char(64)); +--let $clone_err = ER_SPECIFIC_ACCESS_DENIED_ERROR +--source ../include/clone_command.inc +connection default; +SHOW GRANTS FOR 'user_clone'@'localhost'; +--echo # Grant backup privilege to clone user +GRANT RELOAD on *.* to 'user_clone'@'localhost'; +FLUSH PRIVILEGES; +SHOW GRANTS FOR 'user_clone'@'localhost'; +disconnect con1; + +--echo # Without LOCK_TBL privilege +--connect (con1,'localhost','user_clone','123',) +--source ../include/clone_command.inc + +connection default; +disconnect con1; +GRANT LOCK TABLES ON *.* TO 'user_clone'@'localhost'; +FLUSH PRIVILEGES; +SHOW GRANTS FOR 'user_clone'@'localhost'; + +--echo # Trying clone again with all privileges +--connect (con1,'localhost','user_clone','123',) +let $clone_err = 0; +--source ../include/clone_command.inc +connection default; +disconnect con1; +DROP SCHEMA testdb_clone; +DROP USER 'user_clone'@'localhost'; +--rmdir $CLONE_DATADIR +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_aria_log_dir_path.test b/mysql-test/suite/clone/t/local_aria_log_dir_path.test new file mode 100644 index 0000000000000..1765efae772fe --- /dev/null +++ b/mysql-test/suite/clone/t/local_aria_log_dir_path.test @@ -0,0 +1,82 @@ +--source include/have_maria.inc +--source ../include/clone_connection_begin.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--let $datadir=`SELECT @@datadir` +--let $targetdir=$MYSQLTEST_VARDIR/tmp/backup + +if ($ARIA_LOGDIR_MARIADB == '') +{ + --let $ARIA_LOGDIR_MARIADB=$MYSQLTEST_VARDIR/tmp/backup_aria_log_dir_path +} + +if ($ARIA_LOGDIR_FS == '') +{ + --let $ARIA_LOGDIR_FS=$MYSQLTEST_VARDIR/tmp/backup_aria_log_dir_path +} + +--let $server_parameters=--aria-log-file-size=8388608 --aria-log-purge-type=external --loose-aria-log-dir-path=$ARIA_LOGDIR_MARIADB + + +--echo # Restart mariadbd with the test specific parameters +--mkdir $ARIA_LOGDIR_FS +--let restart_noprint=1 +--let $restart_parameters=$server_parameters +--source include/restart_mysqld.inc + + +--echo # Create and populate an Aria table (and Aria logs) +CREATE TABLE t1 (id INT, txt LONGTEXT) ENGINE=Aria; +DELIMITER $$; +BEGIN NOT ATOMIC + FOR id IN 0..9 DO + INSERT INTO test.t1 (id, txt) VALUES (id, REPEAT(id,1024*1024)); + END FOR; +END; +$$ +DELIMITER ;$$ + + +--echo # Testing aria log files before --backup +SET @@global.aria_checkpoint_interval=DEFAULT /*Force checkpoint*/; +--file_exists $ARIA_LOGDIR_FS/aria_log_control +--file_exists $ARIA_LOGDIR_FS/aria_log.00000001 +--file_exists $ARIA_LOGDIR_FS/aria_log.00000002 +--error 1 +--file_exists $ARIA_LOGDIR_FS/aria_log.00000003 +--replace_regex /Size +[0-9]+ ; .+aria_log/aria_log/ +SHOW ENGINE aria logs; + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +--source ../include/clone_command.inc + +let restart_no_print=1; +let restart_parameters=$server_parameters --datadir=$CLONE_DATADIR; +--source include/restart_mysqld.inc +--enable_result_log + +--echo # Check that the table is there after cloning +SELECT COUNT(*) from t1; +DROP TABLE t1; + +--echo # Testing aria log files after clone +SET @@global.aria_checkpoint_interval=DEFAULT /*Force checkpoint*/; +--file_exists $ARIA_LOGDIR_FS/aria_log_control +#--file_exists $ARIA_LOGDIR_FS/aria_log.00000001 +--file_exists $ARIA_LOGDIR_FS/aria_log.00000002 +--error 1 +--file_exists $ARIA_LOGDIR_FS/aria_log.00000003 +--replace_regex /Size +[0-9]+ ; .+aria_log/aria_log/ +SHOW ENGINE aria logs; + + +--echo # Restarting mariadbd with default parameters +--let $restart_parameters= +--source include/restart_mysqld.inc +DROP TABLE t1; +--rmdir $ARIA_LOGDIR_FS +--rmdir $CLONE_DATADIR +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_aria_log_tables.test b/mysql-test/suite/clone/t/local_aria_log_tables.test new file mode 100644 index 0000000000000..310bda5d14a36 --- /dev/null +++ b/mysql-test/suite/clone/t/local_aria_log_tables.test @@ -0,0 +1,58 @@ +--source include/have_aria.inc +--source include/have_debug.inc +--source include/not_embedded.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR= `select @@datadir` + +CREATE TABLE t(i INT) + ENGINE ARIA TRANSACTIONAL=1 ROW_FORMAT=PAGE PAGE_CHECKSUM=1; + +# Truncate the log in order to make the test ./mtr --repeat proof +SET GLOBAL general_log = 0; +TRUNCATE mysql.general_log; +SET GLOBAL general_log = 1; +SET GLOBAL log_output = 'TABLE'; + +INSERT INTO t VALUES (1); + +--replace_column 1 TIMESTAMP 2 USER_HOST 3 THREAD_ID 5 Query +--sorted_result +SELECT * FROM mysql.general_log + WHERE argument LIKE "INSERT INTO %" AND + (command_type = "Query" OR command_type = "Execute") ; + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; + +SET DEBUG_SYNC="after_stage_block_ddl SIGNAL start_dml WAIT_FOR resume_clone1"; +--source ../include/clone_command_send.inc + +--echo # Insert new row into general_log table after it has been copied on BLOCK_DDL. +connect(con1,localhost,root,,); +SET DEBUG_SYNC="now WAIT_FOR start_dml"; +INSERT INTO test.t VALUES(2); +SET DEBUG_SYNC="now SIGNAL resume_clone1"; + +connection default; +reap; +disconnect con1; + +let restart_noprint=1; +let restart_parameters=--datadir=$CLONE_DATADIR; +--source include/restart_mysqld.inc + +--replace_column 1 TIMESTAMP 2 USER_HOST 3 THREAD_ID 5 Query +--sorted_result +SELECT * FROM mysql.general_log + WHERE argument LIKE "INSERT INTO %" AND + (command_type = "Query" OR command_type = "Execute") ; + +let restart_parameters=; +--source include/restart_mysqld.inc +rmdir $CLONE_DATADIR; +DROP TABLE t; +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_aria_rotate_during_backup.opt b/mysql-test/suite/clone/t/local_aria_rotate_during_backup.opt new file mode 100644 index 0000000000000..7c3ebe422c314 --- /dev/null +++ b/mysql-test/suite/clone/t/local_aria_rotate_during_backup.opt @@ -0,0 +1,2 @@ +--loose-aria-log-file-size=8388608 +--loose-restart-for-aria_log_rotate_during_backup="This is needed to recreate datadir, to have Aria start logs from aria_log.00000001" diff --git a/mysql-test/suite/clone/t/local_aria_rotate_during_backup.test b/mysql-test/suite/clone/t/local_aria_rotate_during_backup.test new file mode 100644 index 0000000000000..237268086dfe9 --- /dev/null +++ b/mysql-test/suite/clone/t/local_aria_rotate_during_backup.test @@ -0,0 +1,81 @@ +--source include/have_debug.inc +--source include/have_aria.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR= `select @@datadir` + +SHOW VARIABLES LIKE 'aria_log_file_size'; + +DELIMITER $$; +CREATE PROCEDURE display_aria_log_control(ctrl BLOB) +BEGIN + SELECT HEX(REVERSE(SUBSTRING(ctrl, 42, 4))) AS last_logno; +END; +$$ +DELIMITER ;$$ + +DELIMITER $$; +CREATE PROCEDURE populate_t1() +BEGIN + FOR id IN 0..9 DO + INSERT INTO test.t1 (id, txt) VALUES (id, REPEAT(id,1024*1024)); + END FOR; +END; +$$ +DELIMITER ;$$ + + +CREATE TABLE test.t1(id INT, txt LONGTEXT) ENGINE=Aria; + +--echo # MYSQLD_DATADIR/aria_log_control before --backup +--let ARIA_DATADIR=$MYSQLD_DATADIR +--source include/aria_log_control_load.inc +CALL display_aria_log_control(@aria_log_control); + + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; + +SET DEBUG_SYNC = 'after_scanning_log_files SIGNAL start_dml1 WAIT_FOR resume_clone1'; +--source ../include/clone_command_send.inc + +connect(con1, localhost,root,,); +SET DEBUG_SYNC= 'now WAIT_FOR start_dml1'; +CALL test.populate_t1(); +SET DEBUG_SYNC= 'now SIGNAL resume_clone1'; + +connection default; +reap; +disconnect con1; +--let ARIA_DATADIR=$MYSQLD_DATADIR +--source include/aria_log_control_load.inc +CALL display_aria_log_control(@aria_log_control); + +--echo # targetdir/aria_log_control after cloning +--let ARIA_DATADIR=$CLONE_DATADIR +--source include/aria_log_control_load.inc +CALL display_aria_log_control(@aria_log_control); + +let restart_noprint=1; +let restart_parameters=--datadir=$CLONE_DATADIR; +--source include/restart_mysqld.inc + +--echo # MYSQLD_DATADIR/aria_log_control after restart with clone +--let ARIA_DATADIR=$MYSQLD_DATADIR +--source include/aria_log_control_load.inc +CALL display_aria_log_control(@aria_log_control); + +--echo # Checking that after --restore all t1 data is there +SELECT id, LENGTH(txt) FROM t1 ORDER BY id; + +let restart_parameters=; +--source include/restart_mysqld.inc + +DROP TABLE t1; +DROP PROCEDURE populate_t1; +DROP PROCEDURE display_aria_log_control; +--rmdir $CLONE_DATADIR +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_basic.test b/mysql-test/suite/clone/t/local_basic.test new file mode 100644 index 0000000000000..587aad9eb19db --- /dev/null +++ b/mysql-test/suite/clone/t/local_basic.test @@ -0,0 +1,108 @@ +# Test clone command +--source include/have_innodb.inc +--source ../include/clone_connection_begin.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 char(64), FULLTEXT KEY fts_index(col2))ENGINE=InnoDB; + +INSERT INTO t1 VALUES(10, 'clone row 1'); +INSERT INTO t1 VALUES(20, 'clone row 2'); +INSERT INTO t1 VALUES(30, 'clone row 3'); + +SELECT * from t1 ORDER BY col1; + +SELECT count(*) FROM mysql.general_log; +SELECT count(*) FROM mysql.slow_log; + +# Create MyIsam and CSV tables in common schema +CREATE TABLE t_myisam(col1 INT PRIMARY KEY, col2 char(64)) ENGINE=MyISAM; +INSERT INTO t_myisam VALUES(10, 'myisam not cloned row 1'); +SELECT * from t_myisam ORDER BY col1; + +CREATE TABLE t_csv(col1 INT NOT NULL, col2 char(64) NOT NULL) ENGINE=CSV; +INSERT INTO t_csv VALUES(10, 'csv not cloned row 1'); +SELECT * from t_csv ORDER BY col1; + +# Create MyIsam and CSV tables in separate schema +CREATE SCHEMA non_innodb; +CREATE TABLE non_innodb.t_myisam(col1 INT PRIMARY KEY, col2 char(64)) ENGINE=MyISAM; +INSERT INTO non_innodb.t_myisam VALUES(10, 'myisam not cloned row 1'); +INSERT INTO non_innodb.t_myisam VALUES(20, 'myisam not cloned row 2'); +INSERT INTO non_innodb.t_myisam VALUES(30, 'myisam not cloned row 3'); +SELECT * from non_innodb.t_myisam ORDER BY col1; + +CREATE TABLE non_innodb.t_csv(col1 INT NOT NULL, col2 char(64) NOT NULL) ENGINE=CSV; +INSERT INTO non_innodb.t_csv VALUES(10, 'csv not cloned row 1'); +INSERT INTO non_innodb.t_csv VALUES(20, 'csv not cloned row 2'); +INSERT INTO non_innodb.t_csv VALUES(30, 'csv not cloned row 3'); +SELECT * from non_innodb.t_csv ORDER BY col1; + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS +WHERE PLUGIN_NAME LIKE '%clone%'; + +# Clone data +--source ../include/clone_command.inc + +if (!$clone_remote_replace) { + --connection default + --echo # Restart server on cloned data directory + --let $restart_noprint=1 + --let restart_parameters=--datadir=$CLONE_DATADIR + --source include/restart_mysqld.inc +} + +# Validate data +SELECT * from t1 ORDER BY col1; +INSERT INTO t1 VALUES(40, 'clone row 4'); +SELECT * from t1 ORDER BY col1; + +# Check MyISAM and CSV table data +SELECT * from t_myisam ORDER BY col1; +INSERT INTO t_myisam VALUES(40, 'myisam not cloned row 4'); +SELECT * from t_myisam ORDER BY col1; + +INSERT INTO t_csv VALUES(40, 'csv not cloned row 4'); +SELECT * from t_csv ORDER BY col1; + +INSERT INTO non_innodb.t_myisam VALUES(40, 'myisam not cloned row 4'); +SELECT * from non_innodb.t_myisam ORDER BY col1; + +INSERT INTO non_innodb.t_csv VALUES(40, 'csv not cloned row 4'); +SELECT * from non_innodb.t_csv ORDER BY col1; + +SHOW TABLES; +# Validate query log tables; +SELECT count(*) FROM mysql.general_log; +SELECT count(*) FROM mysql.slow_log; + +SET GLOBAL general_log = ON; +SET GLOBAL slow_query_log = ON; + +#Cleanup +if (!$clone_remote_replace) { + --let restart_parameters= + --source include/restart_mysqld.inc +} + +SHOW TABLES; +SELECT * from t1 ORDER BY col1; + +SELECT * from t_myisam ORDER BY col1; +DROP TABLE t1; + +DROP TABLE t_myisam; +DROP TABLE t_csv; + +DROP TABLE non_innodb.t_myisam; +DROP TABLE non_innodb.t_csv; + +DROP SCHEMA non_innodb; + +if (!$clone_remote_replace) { + --rmdir $CLONE_DATADIR +} +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_boundary.test b/mysql-test/suite/clone/t/local_boundary.test new file mode 100644 index 0000000000000..1027c42c6a104 --- /dev/null +++ b/mysql-test/suite/clone/t/local_boundary.test @@ -0,0 +1,283 @@ +# Test clone with different table types with debug sync +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc +--source include/not_windows.inc + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +--source ../include/create_schema.inc + +# Insert 150 rows +call execute_dml(0, 0, 150, 150, 10, 0); + +# Check base rows +SHOW CREATE TABLE t1; + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +## Test-1: Clone with dirty pages and redo log [No zero copy/sendfile] + +--echo # In connection default - Cloning database +SET GLOBAL DEBUG ="+d,clone_no_zero_copy"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 150 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 150, 200, 100, 1); +COMMIT; +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 150, 200, 100, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--reap +SET GLOBAL DEBUG ="-d,clone_no_zero_copy"; + +disconnect con1; + +--echo # Restart cloned database +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 150, 150, 100, 0); + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Restart and Remove cloned directory +--let restart_parameters= +--source include/restart_mysqld.inc +--rmdir $CLONE_DATADIR + +## Test-2A: Clone with redo log ending at file boundary +#SET GLOBAL innodb_log_file_size = 4*1024*1024; +SET GLOBAL DEBUG ="+d,clone_arch_log_stop_file_end"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 10 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1 WAIT_FOR start_dml2'; + +START TRANSACTION; +# Execute procedure to delete all rows and insert 150 rows +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 150, 150, 10, 0); +COMMIT; + +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--reap + +SET GLOBAL DEBUG ="-d,clone_arch_log_stop_file_end"; + +# Removed cloned data +--rmdir $CLONE_DATADIR + +## Test-2B: Clone with redo log ending at file boundary + some bytes +# SET GLOBAL innodb_redo_log_capacity = 100*1024*1024; +SET GLOBAL DEBUG ="+d,clone_arch_log_stop_file_end"; +SET GLOBAL DEBUG ="+d,clone_arch_log_extra_bytes"; +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 10 Key Range] +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1 WAIT_FOR start_dml2'; + +START TRANSACTION; +# Execute procedure to delete all rows and insert 150 rows +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 150, 150, 10, 0); +COMMIT; + +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--reap + +SET GLOBAL DEBUG ="-d,clone_arch_log_stop_file_end"; +SET GLOBAL DEBUG ="-d,clone_arch_log_extra_bytes"; + +# Removed cloned data +--rmdir $CLONE_DATADIR + +## Test-3A: Shutdown while Clone in progress [FILE_COPY] +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 10 Key Range] +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; +--source include/shutdown_mysqld.inc + +connection default; +# During shutdown it is possible to get "2013" - Lost connection to server +--echo # In connection default - Cloning database +--error ER_QUERY_INTERRUPTED,2013,ER_CLONE_DONOR +--reap + +disconnect con1; + +# Removed cloned data +--rmdir $CLONE_DATADIR +--source include/start_mysqld.inc + +## Test-3B: Shutdown while Clone in progress [PAGE_COPY] +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 10 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; + +START TRANSACTION; +# Execute procedure to delete all rows and insert 50 rows +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 50, 50, 10, 0); +COMMIT; +--source include/shutdown_mysqld.inc + +connection default; +--echo # In connection default - Cloning database +--error ER_QUERY_INTERRUPTED,2013,ER_CLONE_DONOR +--reap + +disconnect con1; + +# Removed cloned data +--rmdir $CLONE_DATADIR +--source include/start_mysqld.inc + +## Test-3C: Shutdown while Clone in progress [REDO_COPY] +SET DEBUG_SYNC = 'RESET'; +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +SET DEBUG_SYNC = 'clone_redo_copy SIGNAL start_dml3 WAIT_FOR resume_clone3'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 10 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 10, 10, 10, 1); +COMMIT; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; + +START TRANSACTION; +# Execute procedure to delete all rows and insert 50 rows +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 50, 50, 10, 0); +COMMIT; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; +SET DEBUG_SYNC = 'now WAIT_FOR start_dml3'; + +START TRANSACTION; +# Execute procedure to delete all rows and insert 50 rows +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 50, 50, 10, 0); +COMMIT; +--source include/shutdown_mysqld.inc + +connection default; +--echo # In connection default - Cloning database +--error ER_QUERY_INTERRUPTED,2013,ER_CLONE_DONOR +--reap + +disconnect con1; + +# Removed cloned data +--rmdir $CLONE_DATADIR +--source include/start_mysqld.inc + +#Cleanup +--source ../include/drop_schema.inc + +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; + +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/clone/t/local_ddl_for_common.test b/mysql-test/suite/clone/t/local_ddl_for_common.test new file mode 100644 index 0000000000000..bcc9247a3b2eb --- /dev/null +++ b/mysql-test/suite/clone/t/local_ddl_for_common.test @@ -0,0 +1,107 @@ +# Test clone command (DDL for non-InnoDB: CSV, MERGE/MRG_MYISAM, MyISAM) +--source include/not_embedded.inc +--source ../include/clone_connection_begin.inc + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS + FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME LIKE '%clone%'; + +# -------- Engine loop: 3->CSV, 2->MERGE, 1->MyISAM -------- +--let $e_myisam = 1 +--let $e_merge = 2 +--let $e_csv = 3 +--let $e_var = $e_csv + +while ($e_var) +{ + if ($e_var == $e_csv) + { + --let $engine = CSV + } + if ($e_var == $e_merge) + { + --let $engine = MERGE + } + if ($e_var == $e_myisam) + { + --let $engine = MyISAM + } + + --echo # ===== Engine iteration: $engine ===== + + # Clean + DROP TABLE IF EXISTS t1, t2, t3, t4, t5, + t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; + + # Create per-engine tables on donor + if ($e_var == $e_merge) + { + # MERGE requires MyISAM base tables + CREATE TABLE t1_m1 (a INT NOT NULL) ENGINE=MyISAM; + CREATE TABLE t1_m2 (a INT NOT NULL) ENGINE=MyISAM; + CREATE TABLE t1 (a INT NOT NULL) + ENGINE=MERGE UNION=(t1_m1, t1_m2) INSERT_METHOD=LAST; + + CREATE TABLE t2_m1 (a INT NOT NULL) ENGINE=MyISAM; + CREATE TABLE t2_m2 (a INT NOT NULL) ENGINE=MyISAM; + CREATE TABLE t2 (a INT NOT NULL) + ENGINE=MERGE UNION=(t2_m1, t2_m2) INSERT_METHOD=LAST; + + CREATE TABLE t3_m1 (a INT NOT NULL) ENGINE=MyISAM; + CREATE TABLE t3_m2 (a INT NOT NULL) ENGINE=MyISAM; + CREATE TABLE t3 (a INT NOT NULL) + ENGINE=MERGE UNION=(t3_m1, t3_m2) INSERT_METHOD=LAST; + } + if ($e_var != $e_merge) + { + eval CREATE TABLE t1 (a INT NOT NULL) ENGINE=$engine; + eval CREATE TABLE t2 (a INT NOT NULL) ENGINE=$engine; + eval CREATE TABLE t3 (a INT NOT NULL) ENGINE=$engine; + } + + # DDL bundle BEFORE clone + CREATE TABLE t4 LIKE t1; + DROP TABLE t2; + RENAME TABLE t3 TO t5; + + # Clone to a fresh directory for this iteration + --let $CLONE_DATADIR=$MYSQL_TMP_DIR/clone_noninnodb_$e_var + --let $clone_err=0 + --let $clone_remote_err=0 + --source ../include/clone_command.inc + + --echo # Restart server on cloned data directory + --let $restart_noprint=1 + --let restart_parameters=--datadir=$CLONE_DATADIR + --source include/restart_mysqld.inc + + # Validate + SELECT COUNT(*) FROM t4; + + --error ER_NO_SUCH_TABLE + SELECT * FROM t2; + + --error ER_NO_SUCH_TABLE + SELECT * FROM t3; + + SELECT COUNT(*) FROM t5; + + # Return to original datadir for next iteration + --let restart_parameters= + --source include/restart_mysqld.inc + + --rmdir $CLONE_DATADIR # delete the cloned dir before next iteration + + # Cleanup donor objects + DROP TABLE IF EXISTS t4, t5, t1, t2, t3, + t1_m1, t1_m2, t2_m1, t2_m2, t3_m1, t3_m2; + + --dec $e_var +} + +UNINSTALL PLUGIN clone; +--source ../include/clone_connection_end.inc diff --git a/mysql-test/suite/clone/t/local_dml.test b/mysql-test/suite/clone/t/local_dml.test new file mode 100644 index 0000000000000..3c967a53d1889 --- /dev/null +++ b/mysql-test/suite/clone/t/local_dml.test @@ -0,0 +1,182 @@ +# Test clone with concurrent DML +--source include/have_innodb.inc +--source ../include/clone_connection_begin.inc + +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $CLONE_DATADIR1 = $MYSQL_TMP_DIR/data_new1 + +--source include/count_sessions.inc + +## Install plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SHOW VARIABLES LIKE "clone_buffer_size"; + +SET GLOBAL clone_buffer_size = 2097152; + +SHOW VARIABLES LIKE "clone_buffer_size"; + +## Create test schema +--source ../include/create_schema.inc + +## Execute Clone while concurrent DMLs are in progress +--let num_rows = 100 + +# Insert 1k rows to run clone for longer and test auto tuning +if ($clone_auto_tune) { +--let num_rows = 1000 +--let clone_throttle = 1 +} + +if ($clone_ddl) { + --let num_rows = 200 + --let clone_throttle = 1 +} + +# Insert rows +--eval call execute_dml(0, 0, $num_rows, $num_rows, 10, 0) + +# Check base rows +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col2, col3 FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3 FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3 FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3 FROM t2 ORDER BY col1 DESC LIMIT 10; + +--echo # In connection con1 - Running Insert Random [100 - 200 Key range] +connect (con1,localhost,root,,); +--send call execute_dml(0, 100, 100, 100, 20, 1) + +--echo # In connection con2 - Running Update Random [0 - 25 Key Range] +connect (con2,localhost,root,,); +--send call execute_dml(1, 0, 25, 100, 20, 1) + +--echo # In connection con3 - Running Delete Random [26 - 50 Key Range] +connect (con3,localhost,root,,); +--send call execute_dml(2, 26, 25, 100, 20, 1) + +--echo # In connection con4 - Running Update Random uncommited [51 - 75 Key Range] +connect (con4,localhost,root,,); + +if ($clone_ddl) { + --send call execute_ddl(10) +} + +if (!$clone_ddl) { + begin; + --send call execute_dml(1, 51, 25, 100, 200, 1) +} + +--echo # In connection default - Cloning database +--connection clone_conn_1 + +--source ../include/clone_command.inc +--echo # In connection default - Finished Cloning + +--echo # In connection con1 - Waiting +connection con1; +--reap + +--echo # In connection con2 - Waiting +connection con2; +--reap + +--echo # In connection con3 - Waiting +connection con3; +--reap + +--echo # In connection con4 - Waiting +connection con4; +--reap +commit; + +--echo # In connection default +connection default; + +disconnect con1; +disconnect con2; +disconnect con3; +disconnect con4; + +--source include/wait_until_count_sessions.inc + +if (!$clone_remote_replace) { + --echo # Restart cloned database + --let restart_noprint=1 + --let restart_parameters=--datadir=$CLONE_DATADIR + --source include/restart_mysqld.inc +} + +if ($clone_remote_replace) { + --connection clone_conn_1 +} + +# Insert 2k rows with throttle to run clone for longer and test auto tuning +if ($clone_auto_tune) { +--let num_rows = 2000 +--let clone_throttle = 0 +} + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +--eval call execute_dml(0, 0, $num_rows, $num_rows, 10, 0) + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col2, col3 FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3 FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3 FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3 FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Test clone after recovering from cloned databse +--let $CLONE_DATADIR = $CLONE_DATADIR1 +--source ../include/clone_command.inc +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +if (!$clone_remote_replace) { + --echo # Restart cloned database + --let restart_parameters=--datadir=$CLONE_DATADIR1 + --source include/restart_mysqld.inc +} + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col2, col3 FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3 FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3 FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3 FROM t2 ORDER BY col1 DESC LIMIT 10; + +#Cleanup +if (!$clone_remote_replace) { +--let restart_parameters= +--source include/restart_mysqld.inc +} +--connection default + +--source ../include/drop_schema.inc + +if (!$clone_remote_replace) { +--rmdir $CLONE_DATADIR +--rmdir $CLONE_DATADIR1 +} + +--source ../include/clone_connection_end.inc + +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_encrypt_compress.test b/mysql-test/suite/clone/t/local_encrypt_compress.test new file mode 100644 index 0000000000000..1d4d266f099ce --- /dev/null +++ b/mysql-test/suite/clone/t/local_encrypt_compress.test @@ -0,0 +1,33 @@ +# Test clone with different table types with debug sync +--source include/have_innodb.inc +--source ../../encryption/include/have_file_key_management_plugin.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR= `select @@datadir;` + +--echo ## Install plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +CREATE TABLE t1(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB)ENGINE=InnoDB PAGE_COMPRESSED=1 ENCRYPTED=YES; + +CREATE TABLE t2(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), col4 BLOB) ENGINE=InnoDB PAGE_COMPRESSED= 1; + +CREATE TABLE t3(col1 INT PRIMARY KEY, col2 int, col3 varchar(64), + col4 BLOB)ENGINE=InnoDB ENCRYPTED=YES; + +let $restart_parameters=--innodb_buffer_pool_load_at_startup=0; +--source include/restart_mysqld.inc +let clone_err=ER_NOT_SUPPORTED_YET; +--source ../include/clone_command.inc +--rmdir $CLONE_DATADIR + +# Modify the schema +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +# Cleanup +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_features.test b/mysql-test/suite/clone/t/local_features.test new file mode 100644 index 0000000000000..bceddc64b6ae8 --- /dev/null +++ b/mysql-test/suite/clone/t/local_features.test @@ -0,0 +1,143 @@ +# Test clone with different table types with debug sync +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +--source ../include/create_schema.inc + +## Execute Clone while concurrent DMLs are in progress + +# Insert 200 rows +call execute_dml(0, 0, 200, 200, 100, 0); + +# Check base rows +SHOW CREATE TABLE t1; + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +## Test-1: Clone with no redo log to copy. +SET GLOBAL innodb_buf_flush_list_now = 1; +--source ../include/clone_command.inc + +--echo # Restart cloned database +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 200, 200, 100, 0); + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; + +--let restart_parameters= +--source include/restart_mysqld.inc +--rmdir $CLONE_DATADIR + +## Test-2: Clone with dirty pages and redo log + +--echo # In connection default - Cloning database +# Bug#32340112 Clone should not depend on server idle timeout +if($remote_clone) { + SET GLOBAL wait_timeout = 1; + SET GLOBAL CLONE_DONOR_TIMEOUT_AFTER_NETWORK_FAILURE = 0; +} + +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 200 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; + +if($remote_clone) { + SET GLOBAL wait_timeout = default; +} + +START TRANSACTION; +CALL execute_dml(1, 0, 200, 500, 100, 1); +COMMIT; +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; + +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 200, 500, 100, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--reap + +disconnect con1; + +--echo # Restart cloned database +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 200, 200, 100, 0); + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Restart and Remove cloned directory +--let restart_parameters= +--source include/restart_mysqld.inc +--rmdir $CLONE_DATADIR + +# Cleanup +--source ../include/drop_schema.inc + +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; + +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/clone/t/local_file_extend.opt b/mysql-test/suite/clone/t/local_file_extend.opt new file mode 100644 index 0000000000000..497233a1520df --- /dev/null +++ b/mysql-test/suite/clone/t/local_file_extend.opt @@ -0,0 +1 @@ +--innodb_sys_tablespaces diff --git a/mysql-test/suite/clone/t/local_file_extend.test b/mysql-test/suite/clone/t/local_file_extend.test new file mode 100644 index 0000000000000..f87d46a88c7c3 --- /dev/null +++ b/mysql-test/suite/clone/t/local_file_extend.test @@ -0,0 +1,175 @@ +# Test clone when tablespace file size is increasing in different stages +# This would follow a rollback during recovery + +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +--source ../include/create_schema.inc + +## Execute Clone while concurrent DMLs are in progress + +# Insert 20 rows +call execute_dml(0, 0, 20, 20, 10, 0); + +# Check base rows +SHOW CREATE TABLE t1; + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +## Test-1: Extend tablespace file during file copy + +--echo # In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_insert1 WAIT_FOR resume_clone1'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Insert [20 Rows - No commit] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_insert1'; +START TRANSACTION; +SELECT FILE_SIZE into @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +call execute_dml(0, 50, 20, 20, 500, 0); +SELECT FILE_SIZE > @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +connection default; +--echo # In connection default - Cloning database +--reap + +--echo # In connection con1 +connection con1; +ROLLBACK; + +connection default; +--echo # In connection default - Cloning database +disconnect con1; + +--echo # Restart cloned database +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 10, 10, 2, 0); +commit; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +#Cleanup +--let restart_parameters= +--source include/restart_mysqld.inc +--rmdir $CLONE_DATADIR + +## Test-2: Extend tablespace file during page copy + +--echo # In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml WAIT_FOR resume_clone2'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_insert2 WAIT_FOR resume_clone3'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Insert [20 Rows - No commit] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml'; +START TRANSACTION; +SELECT FILE_SIZE into @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +call execute_dml(1, 0, 20, 20, 10, 1); +SELECT FILE_SIZE > @t1_file_size FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'test/t1'; +COMMIT; + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection con1; +SET DEBUG_SYNC = 'now WAIT_FOR start_insert2'; +START TRANSACTION; +call execute_dml(0, 50, 20, 20, 500, 0); + +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone3'; + +connection default; +--echo # In connection default - Cloning database +--replace_result $CLONE_DATADIR CLONE_DATADIR +--reap + +--echo # In connection con1 +connection con1; +ROLLBACK; + +connection default; +--echo # In connection default - Cloning database +disconnect con1; + +--echo # Restart cloned database +--replace_result $CLONE_DATADIR CLONE_DATADIR +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 10, 10, 2, 0); +commit; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +#Cleanup +--let restart_parameters= +--source include/restart_mysqld.inc + +--source ../include/drop_schema.inc + +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; + +--source include/wait_until_count_sessions.inc +--rmdir $CLONE_DATADIR diff --git a/mysql-test/suite/clone/t/local_insert.test b/mysql-test/suite/clone/t/local_insert.test new file mode 100644 index 0000000000000..63c9b2f578c97 --- /dev/null +++ b/mysql-test/suite/clone/t/local_insert.test @@ -0,0 +1,98 @@ +# Test clone with insert + +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); +connect (con3,localhost,root,,); + +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR = `SELECT @@datadir` + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +DELIMITER |; +CREATE PROCEDURE prepare_data(IN val INT) +BEGIN + DECLARE i INT DEFAULT 1; + + WHILE i <= val DO + INSERT INTO t1 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); + INSERT INTO t2 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); + SET i = i + 1; + END WHILE; +END| +DELIMITER ;| + +--echo # Case 1 - Normal page archiving process using clone client. + +CREATE TABLE t1 (a INT AUTO_INCREMENT, b LONGBLOB, c LONGBLOB, key k1(a))ENGINE=InnoDB; +CREATE TABLE t2 (a INT PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; + +--connection con1 +SET DEBUG_SYNC = 'clone_file_copy SIGNAL page_signal WAIT_FOR go_page'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL redo_signal WAIT_FOR go_redo'; + +if (!$remote_clone) { + SET DEBUG_SYNC = 'clone_donor_after_saving_dynamic_metadata SIGNAL meta_signal WAIT_FOR go_meta'; +} +--source ../include/clone_command_send.inc + +--connection con2 +SET DEBUG_SYNC = 'now WAIT_FOR page_signal'; +CALL prepare_data(50); +SET DEBUG_SYNC = 'now SIGNAL go_page'; + +SET DEBUG_SYNC = 'now WAIT_FOR redo_signal'; +CALL prepare_data(10); +SET DEBUG_SYNC = 'now SIGNAL go_redo'; + +if (!$remote_clone) { + SET DEBUG_SYNC = 'now WAIT_FOR meta_signal'; +#SET GLOBAL debug="+d,periodical_checkpoint_disabled"; + CALL prepare_data(10); + UPDATE t1 SET a = 100 where a = 1; + UPDATE t2 SET a = 200 where a = 1; + SET DEBUG_SYNC = 'now SIGNAL go_meta'; +} + +--connection con1 +--reap + +select count(*), max(a) from t1; +select count(*), max(a) from t2; + +--echo # Restart server on cloned data directory +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +select count(*), max(a) from t1; +select count(*), max(a) from t2; + +INSERT INTO t1 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); +INSERT INTO t2 (b,c) VALUES (REPEAT(a,600), REPEAT(b,600)); + +#Cleanup +--let restart_parameters= +--source include/restart_mysqld.inc + +--connection con1 +DROP TABLE t1; +DROP TABLE t2; + +SET DEBUG_SYNC = 'RESET'; + +--rmdir $CLONE_DATADIR + +DROP PROCEDURE prepare_data; + +UNINSTALL PLUGIN clone; + +--disconnect con1 +--disconnect con2 +--disconnect con3 diff --git a/mysql-test/suite/clone/t/local_partition.test b/mysql-test/suite/clone/t/local_partition.test new file mode 100644 index 0000000000000..21243c505f8a4 --- /dev/null +++ b/mysql-test/suite/clone/t/local_partition.test @@ -0,0 +1,102 @@ +--echo # DDL churn + partitions in InnoDB and MyISAM +--source include/have_innodb.inc +--source include/have_partition.inc +--source include/not_embedded.inc +--source ../include/clone_connection_begin.inc + +# Install Clone plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS + FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME LIKE '%clone%'; + +DROP TABLE IF EXISTS t1, t2, p, isam_t1, isam_t2, isam_p; + +CREATE TABLE t1(a INT) ENGINE=InnoDB; +INSERT INTO t1 VALUES (1),(2),(3); + +CREATE TABLE t2(a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (4),(5),(6); + +CREATE TABLE p(a INT) +ENGINE=InnoDB +PARTITION BY RANGE (a) +( + PARTITION p0 VALUES LESS THAN (100), + PARTITION p1 VALUES LESS THAN (200), + PARTITION p2 VALUES LESS THAN (300), + PARTITION p3 VALUES LESS THAN (400) +); +INSERT INTO p VALUES (1),(101),(201),(301); + +CREATE TABLE isam_t1(a INT) ENGINE=MyISAM; +INSERT INTO isam_t1 VALUES (1),(2),(3); + +CREATE TABLE isam_t2(a INT) ENGINE=MyISAM; +INSERT INTO isam_t2 VALUES (4),(5),(6); + +CREATE TABLE isam_p(a INT) +ENGINE=MyISAM +PARTITION BY RANGE (a) +( + PARTITION p0 VALUES LESS THAN (100), + PARTITION p1 VALUES LESS THAN (200), + PARTITION p2 VALUES LESS THAN (300), + PARTITION p3 VALUES LESS THAN (400) +); +INSERT INTO isam_p VALUES (1),(101),(201),(301); + +# Perform DDL and DML +DROP TABLE t1; +DROP TABLE t2; +CREATE TABLE t2(a INT) ENGINE=InnoDB; +INSERT INTO t2 VALUES (40),(50),(60); # new Data + +ALTER TABLE p DROP PARTITION p0; +ALTER TABLE p DROP PARTITION p1; +ALTER TABLE p ADD PARTITION (PARTITION p4 VALUES LESS THAN (500)); +ALTER TABLE p ADD PARTITION (PARTITION p5 VALUES LESS THAN (600)); +INSERT INTO p VALUES (401),(501); # final p rows should be 201,301,401,501 + +DROP TABLE isam_t1; +DROP TABLE isam_t2; +CREATE TABLE isam_t2(a INT) ENGINE=MyISAM; +INSERT INTO isam_t2 VALUES (40),(50),(60); # new Data + +ALTER TABLE isam_p DROP PARTITION p0; +ALTER TABLE isam_p DROP PARTITION p1; +ALTER TABLE isam_p ADD PARTITION (PARTITION p4 VALUES LESS THAN (500)); +ALTER TABLE isam_p ADD PARTITION (PARTITION p5 VALUES LESS THAN (600)); +INSERT INTO isam_p VALUES (401),(501); # final isam_p rows: 201,301,401,501 + +# Clone Data +--connection clone_conn_1 +--let $CLONE_DATADIR=$MYSQL_TMP_DIR/clone_ddl_partitions +--source ../include/clone_command.inc + +# Start server with cloned data +--connection default +--echo # Restart server on cloned data directory +--let $restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Validata +--error ER_NO_SUCH_TABLE +SELECT * FROM t1; +SELECT * FROM t2 ORDER BY a; +SELECT * FROM p ORDER BY a; +--error ER_NO_SUCH_TABLE +SELECT * FROM isam_t1; +SELECT * FROM isam_t2 ORDER BY a; +SELECT * FROM isam_p ORDER BY a; +# Restart server with original data +--let restart_parameters= +--source include/restart_mysqld.inc +--connection default +DROP TABLE IF EXISTS p, t2, isam_p, isam_t2; +--rmdir $CLONE_DATADIR +--source ../include/clone_connection_end.inc +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_partition_notwin.test b/mysql-test/suite/clone/t/local_partition_notwin.test new file mode 100644 index 0000000000000..a5d37b7158473 --- /dev/null +++ b/mysql-test/suite/clone/t/local_partition_notwin.test @@ -0,0 +1,49 @@ +--echo # MyISAM table with 400 partitions +--source include/have_partition.inc +--source include/not_embedded.inc +--source ../include/clone_connection_begin.inc + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS + FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME LIKE '%clone%'; + +DROP TABLE IF EXISTS t1; + +CREATE TABLE t1 ( + id BIGINT NOT NULL AUTO_INCREMENT, + PRIMARY KEY (id) +) ENGINE=MyISAM +PARTITION BY HASH (id) +PARTITIONS 400; + +insert into t1 values (1),(2),(3),(4); + +--echo # clone Begins +--let $CLONE_DATADIR=$MYSQL_TMP_DIR/clone_partition_400 +--source ../include/clone_command.inc + +# Restart with clone data +--connection default +--echo # Restart server on cloned data directory +--let $restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Validate +SHOW CREATE TABLE t1; +SELECT COUNT(*) FROM t1; + +--let restart_parameters= +--source include/restart_mysqld.inc + +--connection default +# Cleanup +DROP TABLE t1; +--rmdir $CLONE_DATADIR + +UNINSTALL PLUGIN clone; +--source ../include/clone_connection_end.inc diff --git a/mysql-test/suite/clone/t/local_stage.test b/mysql-test/suite/clone/t/local_stage.test new file mode 100644 index 0000000000000..495a8d1dfccff --- /dev/null +++ b/mysql-test/suite/clone/t/local_stage.test @@ -0,0 +1,96 @@ +# Test clone with debug sync point to ensure concurrent operation and data in each stages +--source include/have_innodb.inc +--source include/no_valgrind_without_big.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +--source ../include/create_schema.inc + +## Execute Clone while concurrent DMLs are in progress + +# Insert 1k rows +call execute_dml(0, 0, 100, 100, 10, 0); + +# Check base rows +SHOW CREATE TABLE t1; + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +--echo # In connection default - Cloning database +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL start_dml2 WAIT_FOR resume_clone2'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 - Running Update Random [0 - 100 Key Range] +connect (con1,localhost,root,,); +SET DEBUG_SYNC = 'now WAIT_FOR start_dml1'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 500, 50, 1); +COMMIT; +--echo # Flush all dirty buffers +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL resume_clone1'; + +SET DEBUG_SYNC = 'now WAIT_FOR start_dml2'; +START TRANSACTION; +CALL execute_dml(1, 0, 100, 300, 50, 1); +COMMIT; +SET DEBUG_SYNC = 'now SIGNAL resume_clone2'; + +connection default; +--echo # In connection default - Cloning database +--reap + +disconnect con1; + +--echo # Restart cloned database +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SHOW CREATE TABLE t2; +SELECT count(*) from t2; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 100, 100, 10, 0); + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1 DESC LIMIT 10; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 LIMIT 10; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t2 ORDER BY col1 DESC LIMIT 10; + +#Cleanup +--let restart_parameters= +--source include/restart_mysqld.inc + +--source ../include/drop_schema.inc + +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; + +--source include/wait_until_count_sessions.inc +--rmdir $CLONE_DATADIR diff --git a/mysql-test/suite/clone/t/local_stage_sys.opt b/mysql-test/suite/clone/t/local_stage_sys.opt new file mode 100644 index 0000000000000..babf6cb620d5b --- /dev/null +++ b/mysql-test/suite/clone/t/local_stage_sys.opt @@ -0,0 +1,2 @@ +--innodb-data-file-path=ibdata1:12M;ibdata2:4M;ibdata3:4M:autoextend +--innodb_file_per_table=OFF diff --git a/mysql-test/suite/clone/t/local_stage_sys.test b/mysql-test/suite/clone/t/local_stage_sys.test new file mode 100644 index 0000000000000..783754d71b25b --- /dev/null +++ b/mysql-test/suite/clone/t/local_stage_sys.test @@ -0,0 +1,2 @@ +# Test clone with concurrent DML on table stored in multi file system tablespace +--source local_stage.test diff --git a/mysql-test/suite/clone/t/local_vector.test b/mysql-test/suite/clone/t/local_vector.test new file mode 100644 index 0000000000000..494624668cdd3 --- /dev/null +++ b/mysql-test/suite/clone/t/local_vector.test @@ -0,0 +1,84 @@ +--echo # Clone test for VECTOR data type and indexes (InnoDB + MyISAM) +--source include/have_innodb.inc +--source include/not_embedded.inc +--source ../include/clone_connection_begin.inc + +# Install Clone Plugin +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +SELECT PLUGIN_NAME, PLUGIN_STATUS + FROM INFORMATION_SCHEMA.PLUGINS + WHERE PLUGIN_NAME LIKE '%clone%'; + +# Creating donor tables + +DROP TABLE IF EXISTS t_innodb, t_myisam; + +CREATE TABLE t_innodb ( + id INT AUTO_INCREMENT PRIMARY KEY, + v VECTOR(5) NOT NULL, + VECTOR INDEX (v) +) ENGINE=InnoDB; + +INSERT INTO t_innodb (v) VALUES + (Vec_FromText('[0.418,0.809,0.823,0.598,0.033]')), + (Vec_FromText('[0.687,0.789,0.496,0.574,0.917]')), + (Vec_FromText('[0.333,0.962,0.467,0.448,0.475]')); + +CREATE TABLE t_myisam ( + a INT, + v VECTOR(1) NOT NULL, + VECTOR(v) +) ENGINE=MyISAM; + +INSERT INTO t_myisam VALUES + (1, 0x31313131), + (2, 0x32323232); + +# Verify queries work before clone +SELECT * FROM t_innodb + ORDER BY vec_distance_euclidean(v, Vec_FromText('[1,0,0,0,0]')) + LIMIT 1; + +SELECT * FROM t_myisam + ORDER BY vec_distance_euclidean(v, 0x30303030) + LIMIT 1; + +# Starting a local clone +--connection clone_conn_1 +--let $CLONE_DATADIR=$MYSQL_TMP_DIR/clone_vector_test +--source ../include/clone_command.inc + +--echo # Restart server on cloned data directory +--connection default +--let $restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Validate on Clone + +SHOW CREATE TABLE t_innodb; +SHOW CREATE TABLE t_myisam; + +SELECT id, Vec_ToText(v) FROM t_innodb; +SELECT a, Vec_ToText(v) FROM t_myisam; + +# Run queries again after clone +SELECT id FROM t_innodb + ORDER BY vec_distance_euclidean(v, Vec_FromText('[1,0,0,0,0]')) + LIMIT 1; + +SELECT * FROM t_myisam + ORDER BY vec_distance_euclidean(v, 0x30303030) + LIMIT 1; + +--let restart_parameters= +--source include/restart_mysqld.inc + +# Cleanup +--connection default +DROP TABLE t_innodb, t_myisam; +--rmdir $CLONE_DATADIR +--source ../include/clone_connection_end.inc +UNINSTALL PLUGIN clone; diff --git a/mysql-test/suite/clone/t/local_xa.test b/mysql-test/suite/clone/t/local_xa.test new file mode 100644 index 0000000000000..fc205f32673e2 --- /dev/null +++ b/mysql-test/suite/clone/t/local_xa.test @@ -0,0 +1,72 @@ +# Test clone with different table types with debug sync +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc + +call mtr.add_suppression("\\[Warning\\] Found 1 prepared XA transactions"); + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +--source ../include/create_schema.inc + +call execute_dml(0, 0, 10, 10, 1, 0); +commit; + +--echo ## Test: Clone with XA transactions +XA start 'xa_trx_1'; +update t1 set col2 = 100; +XA end 'xa_trx_1'; +XA prepare 'xa_trx_1'; + +connect(con1,localhost,root,,,); +--echo # In connection default - Start Cloning database +--source ../include/clone_command.inc +--echo # In connection con1 - Finish XA prepare, Start XA commit +connection default; +XA commit 'xa_trx_1'; + +--echo # In connection default +disconnect con1; + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; + +--echo # Restart cloned database +--let restart_noprint=1 +--let restart_parameters=--datadir=$CLONE_DATADIR +--source include/restart_mysqld.inc + +# Check table in cloned database +SHOW CREATE TABLE t1; +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; + +XA recover; +XA commit 'xa_trx_1'; + +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; + +# Execute procedure to delete all rows and insert +call execute_dml(3, 0, 1, 1, 1, 0); +call execute_dml(0, 0, 10, 10, 1, 0); + +SELECT count(*) from t1; +SELECT col1, col2, col3, SUBSTRING(col4, 1000, 32) FROM t1 ORDER BY col1; + +# Restart and Remove cloned directory +--let restart_parameters= +--source include/restart_mysqld.inc +--rmdir $CLONE_DATADIR + +# Cleanup +--source ../include/drop_schema.inc + +UNINSTALL PLUGIN clone; +SET DEBUG_SYNC = 'RESET'; + +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/clone/t/monitor_progress.test b/mysql-test/suite/clone/t/monitor_progress.test new file mode 100644 index 0000000000000..0c09c17870e37 --- /dev/null +++ b/mysql-test/suite/clone/t/monitor_progress.test @@ -0,0 +1,286 @@ +# Monitor clone operations using performance schema's stage and statement events. +--source include/have_innodb.inc +--source include/have_debug.inc +--source include/have_debug_sync.inc +--source include/count_sessions.inc +--source include/not_embedded.inc + +# Disable PFS monitoring for threads by default +connect (con1,localhost,root,,); +CALL sys.ps_setup_disable_thread(CONNECTION_ID()); + +connect (con2,localhost,root,,); +CALL sys.ps_setup_disable_thread(CONNECTION_ID()); + +connect (con3,localhost,root,,); +CALL sys.ps_setup_disable_thread(CONNECTION_ID()); + +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new +--let $MYSQLD_DATADIR = `SELECT @@datadir` + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +# Enable all the required PFS instruments. +--replace_regex /[0-9]/X/ +CALL sys.ps_setup_enable_instrument('%stage/innodb/clone%'); +--replace_regex /[0-9]/X/ +CALL sys.ps_setup_enable_instrument('statement/clone/%'); + +--replace_regex /[0-9]/X/ +CALL sys.ps_setup_enable_consumer('events_statements%'); +--replace_regex /[0-9]/X/ +CALL sys.ps_setup_enable_consumer('events_stages%'); + +--disable_ps_protocol +SELECT * +FROM performance_schema.setup_instruments +WHERE name LIKE "%stage/innodb/clone%" +OR name LIKE "statement/clone/%" +OR name LIKE "wait/io/file/innodb/innodb_clone_file" +ORDER BY NAME; + +SELECT * +FROM performance_schema.setup_consumers +WHERE name LIKE "events_statements_%" OR name LIKE "events_stages_%" +ORDER BY NAME; + +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; + +--echo # Case 1 - Monitoring a normal Clone operation. +--connection con1 +CALL sys.ps_setup_enable_thread(CONNECTION_ID()); +--source ../include/clone_command.inc + +--replace_regex /FROM '.*'@'.*':[0-9]+ /FROM USER@HOST:PORT / /DATA DIRECTORY = '.*'/DATA DIRECTORY = '$CLONE_DATADIR'/ +SELECT EVENT_NAME, TIMER_START > 0, TIMER_END > 0, TIMER_WAIT > 0, +SQL_TEXT, CURRENT_SCHEMA +FROM performance_schema.events_statements_history_long +WHERE event_name LIKE "statement/clone/%" +ORDER BY EVENT_NAME; + +SELECT EVENT_NAME, TIMER_START > 0, +TIMER_END > 0, WORK_COMPLETED = WORK_ESTIMATED +FROM performance_schema.events_stages_history_long +WHERE event_name LIKE "%stage/innodb/clone%" +ORDER BY EVENT_NAME; + +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; + +--rmdir $CLONE_DATADIR + +--echo # Case 2 - Monitoring Clone operation which has more estimated work +--echo # during file and page copy stage than in a default run. + +--connection con1 + +DELIMITER |; +CREATE PROCEDURE prepare_data(IN val INT) +BEGIN + DECLARE i INT DEFAULT 0; + + WHILE i < val DO + INSERT INTO t1 (b,c) VALUES (REPEAT(a,500), REPEAT(b,100)); + INSERT INTO t2 (b,c) VALUES (REPEAT(a,500), REPEAT(b,100)); + INSERT INTO t3 (b,c) VALUES (REPEAT(a,500), REPEAT(b,100)); + SET i = i + 1; + END WHILE; +END| +DELIMITER ;| + +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; + +SET GLOBAL innodb_buf_flush_list_now = 1; + +SET DEBUG_SYNC = 'clone_file_copy SIGNAL page_signal WAIT_FOR go_page'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL redo_signal WAIT_FOR go_redo'; + +--source ../include/clone_command_send.inc + +--connection con2 +SET DEBUG_SYNC = 'now WAIT_FOR page_signal'; + +# Insert data in the middle of file copy to add extra pages which will +# need to be copied across during page copy. +CALL prepare_data(50); +SET GLOBAL innodb_buf_flush_list_now = 1; + +SET DEBUG_SYNC = 'now SIGNAL go_page'; + +# Check PFS statement event and insert data in the middle of page copy +# to add extra redo chunks to be copied across during redo copy. + +--connection con3 +SET DEBUG_SYNC = 'now WAIT_FOR redo_signal'; + +--replace_regex /FROM '.*'@'.*':[0-9]+ /FROM USER@HOST:PORT / /DATA DIRECTORY = '.*'/DATA DIRECTORY = '$CLONE_DATADIR'/ +SELECT EVENT_NAME, TIMER_START > 0, TIMER_END > 0, TIMER_WAIT > 0, +SQL_TEXT, CURRENT_SCHEMA +FROM performance_schema.events_statements_current +WHERE event_name LIKE "statement/clone/%" +ORDER BY EVENT_NAME; + +CALL prepare_data(50); + +SET DEBUG_SYNC = 'now SIGNAL go_redo'; + +--connection con1 +--reap + +# Check PFS stage and statements event in their corresponding +# history_long tables. + +SELECT EVENT_NAME, WORK_COMPLETED > 0, TIMER_START > 0, +TIMER_END > 0, WORK_COMPLETED = WORK_ESTIMATED +FROM performance_schema.events_stages_history_long +WHERE event_name LIKE "%stage/innodb/clone%" +ORDER BY EVENT_NAME; + +--replace_regex /FROM '.*'@'.*':[0-9]+ /FROM USER@HOST:PORT / /DATA DIRECTORY = '.*'/DATA DIRECTORY = '$CLONE_DATADIR'/ +SELECT EVENT_NAME, TIMER_START > 0, TIMER_END > 0, TIMER_WAIT > 0, +SQL_TEXT, CURRENT_SCHEMA +FROM performance_schema.events_statements_history_long +WHERE event_name LIKE "statement/clone/%"; + +SET DEBUG_SYNC='RESET'; + +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; + +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +--rmdir $CLONE_DATADIR + +--echo # Case 3 - Monitoring progress in the middle of file copy. + +--connection con1 +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b INT); +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b INT); +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b INT); + +SET DEBUG_SYNC = 'clone_file_copy SIGNAL file_signal WAIT_FOR go_file'; +--source ../include/clone_command_send.inc + +--connection con2 +SET DEBUG_SYNC= 'now WAIT_FOR file_signal'; + +SELECT EVENT_NAME, WORK_COMPLETED <= WORK_ESTIMATED +FROM performance_schema.events_stages_current +WHERE event_name LIKE "%file copy%" +ORDER BY EVENT_NAME; + +SET DEBUG_SYNC= 'now SIGNAL go_file'; + +--connection con1 +--reap +SET DEBUG_SYNC = 'RESET'; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; + +--rmdir $CLONE_DATADIR + +--echo # Case 4 - Monitoring progress in the middle of page copy. +--connection con1 +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +SET GLOBAL innodb_buf_flush_list_now = 1; + +SET DEBUG_SYNC = 'clone_file_copy SIGNAL page_signal WAIT_FOR go_page'; +SET DEBUG_SYNC = 'clone_page_copy SIGNAL page_middle_signal WAIT_FOR go_page_middle'; +--source ../include/clone_command_send.inc + +--connection con2 +SET DEBUG_SYNC = 'now WAIT_FOR page_signal'; +CALL prepare_data(50); +SET GLOBAL innodb_buf_flush_list_now = 1; +SET DEBUG_SYNC = 'now SIGNAL go_page'; + +--connection con3 +SET DEBUG_SYNC = 'now WAIT_FOR page_middle_signal'; + +SELECT EVENT_NAME, WORK_COMPLETED <= WORK_ESTIMATED +FROM performance_schema.events_stages_current +WHERE event_name LIKE "%page copy%" +ORDER BY EVENT_NAME; + +SET DEBUG_SYNC = 'now SIGNAL go_page_middle'; + +--connection con1 +--reap +SET DEBUG_SYNC = 'RESET'; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +TRUNCATE TABLE performance_schema.events_stages_history; +TRUNCATE TABLE performance_schema.events_stages_history_long; +TRUNCATE TABLE performance_schema.events_statements_history; +TRUNCATE TABLE performance_schema.events_statements_history_long; + +--rmdir $CLONE_DATADIR + +--echo # Case 5 - Monitoring progress in the middle of redo copy. + +--connection con1 +CREATE TABLE t1 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t2 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; +CREATE TABLE t3 (a INT NOT NULL PRIMARY KEY AUTO_INCREMENT, b LONGBLOB, c LONGBLOB)ENGINE=InnoDB; + +SET DEBUG_SYNC = 'clone_page_copy SIGNAL redo_signal WAIT_FOR go_redo'; +SET DEBUG_SYNC = 'clone_redo_copy SIGNAL redo_middle_signal WAIT_FOR go_redo_middle'; +--source ../include/clone_command_send.inc + +--connection con2 +SET DEBUG_SYNC= 'now WAIT_FOR redo_signal'; + +CALL prepare_data(50); +SET DEBUG_SYNC= 'now SIGNAL go_redo'; + +--connection con3 +SET DEBUG_SYNC = 'now WAIT_FOR redo_middle_signal'; + +SELECT EVENT_NAME, WORK_COMPLETED <= WORK_ESTIMATED +FROM performance_schema.events_stages_current +WHERE event_name LIKE "%redo copy%" +ORDER BY EVENT_NAME; + +SET DEBUG_SYNC = 'now SIGNAL go_redo_middle'; +--connection con1 +--reap +SET DEBUG_SYNC = 'RESET'; +DROP PROCEDURE prepare_data; + +USE test; +DROP TABLE t1; +DROP TABLE t2; +DROP TABLE t3; + +--connection default + +--rmdir $CLONE_DATADIR +UNINSTALL PLUGIN clone; + +--disconnect con1 +--disconnect con2 +--disconnect con3 + +--source include/wait_until_count_sessions.inc diff --git a/mysql-test/suite/clone/t/redo_log_resize.test b/mysql-test/suite/clone/t/redo_log_resize.test new file mode 100644 index 0000000000000..2efb43ddd9a27 --- /dev/null +++ b/mysql-test/suite/clone/t/redo_log_resize.test @@ -0,0 +1,44 @@ +--source include/have_innodb.inc +--source include/have_debug_sync.inc +--source include/not_embedded.inc + +## Install plugin +--let $CLONE_DATADIR = $MYSQL_TMP_DIR/data_new + +--replace_result $MARIADB_CLONE_SO CLONE_PLUGIN +--eval INSTALL PLUGIN clone SONAME '$MARIADB_CLONE_SO' + +## Create test schema +disable_query_log; +--source ../include/create_schema.inc +enable_query_log; + +SET DEBUG_SYNC = 'clone_file_copy SIGNAL start_dml1 WAIT_FOR resume_clone1'; +--source ../include/clone_command_send.inc + +--echo # In connection con1 +connect (con1,localhost,root,,); +SET DEBUG_SYNC='now WAIT_FOR start_dml1'; +--error ER_CLONE_IN_PROGRESS +SET global innodb_log_file_size=4*1024*1024; +SET DEBUG_SYNC= 'now SIGNAL resume_clone1'; + +connection default; +reap; +SET DEBUG_SYNC="redo_log_resizing SIGNAL clone_start WAIT_FOR redo_finish"; +send SET global innodb_log_file_size=4*1024*1024; + +connection con1; +set DEBUG_SYNC="now WAIT_FOR clone_start"; +rmdir $CLONE_DATADIR; +let $clone_err= ER_CLONE_DDL_IN_PROGRESS; +--source include/clone_command.inc +set DEBUG_SYNC="now SIGNAL redo_finish"; + +connection default; +reap; +disconnect con1; +--source ../include/drop_schema.inc +UNINSTALL PLUGIN clone; +SET GLOBAL innodb_log_file_size = 10 *1024*1024; +SET DEBUG_SYNC= 'RESET'; diff --git a/mysql-test/suite/clone/t/suite.opt b/mysql-test/suite/clone/t/suite.opt new file mode 100644 index 0000000000000..3d385b83ea9fa --- /dev/null +++ b/mysql-test/suite/clone/t/suite.opt @@ -0,0 +1,3 @@ +$CLONE_PLUGIN_OPT +$EXAMPLE_PLUGIN_OPT +--log_error_verbosity=3 diff --git a/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result b/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result index 4fa9593444652..5ae6964d878bc 100644 --- a/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result +++ b/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result @@ -215,6 +215,10 @@ icp_attempts icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter icp_no_match icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition does not match icp_out_of_range icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition out of range icp_match icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition matches +page_track_resets page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of resets +page_track_partial_block_writes page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of partial block writes +page_track_full_block_writes page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of full block writes +page_track_checkpoint_partial_flush_request page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of partial flush requests made during checkpointing select * from information_schema.innodb_ft_default_stopword; value a diff --git a/mysql-test/suite/innodb/r/monitor.result b/mysql-test/suite/innodb/r/monitor.result index d97f741efdd75..137ae38cf9959 100644 --- a/mysql-test/suite/innodb/r/monitor.result +++ b/mysql-test/suite/innodb/r/monitor.result @@ -180,6 +180,10 @@ icp_attempts disabled icp_no_match disabled icp_out_of_range disabled icp_match disabled +page_track_resets disabled +page_track_partial_block_writes disabled +page_track_full_block_writes disabled +page_track_checkpoint_partial_flush_request disabled create temporary table orig_innodb_metrics as select name, enabled from information_schema.innodb_metrics; set global innodb_monitor_disable = All; select name from information_schema.innodb_metrics where enabled; @@ -220,6 +224,8 @@ lock_row_lock_time disabled lock_row_lock_time_max disabled lock_row_lock_waits disabled lock_row_lock_time_avg disabled +page_track_partial_block_writes disabled +page_track_full_block_writes disabled set global innodb_monitor_enable = "%lock*"; ERROR 42000: Variable 'innodb_monitor_enable' can't be set to the value of '%lock*' set global innodb_monitor_enable="%%%%%%%%%%%%%%%%%%%%%%%%%%%"; diff --git a/mysql-test/suite/mariabackup/aria_log_rotate_during_backup.test b/mysql-test/suite/mariabackup/aria_log_rotate_during_backup.test index 172ade338d55a..9b77a5f54b974 100644 --- a/mysql-test/suite/mariabackup/aria_log_rotate_during_backup.test +++ b/mysql-test/suite/mariabackup/aria_log_rotate_during_backup.test @@ -34,11 +34,11 @@ CREATE TABLE test.t1(id INT, txt LONGTEXT) ENGINE=Aria; --source include/aria_log_control_load.inc CALL display_aria_log_control(@aria_log_control); - +let $backuplog= $MYSQLTEST_VARDIR/tmp/backup.log; --echo # Running --backup --let after_scanning_log_files=CALL test.populate_t1 --disable_result_log ---exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --parallel=10 --target-dir=$targetdir --dbug=+d,mariabackup_events 2>&1 +--exec $XTRABACKUP --defaults-file=$MYSQLTEST_VARDIR/my.cnf --backup --target-dir=$targetdir --dbug=+d,mariabackup_events > $backuplog --let after_scanning_log_files= --enable_result_log @@ -77,6 +77,7 @@ CALL display_aria_log_control(@aria_log_control); SELECT id, LENGTH(txt) FROM t1 ORDER BY id; DROP TABLE t1; rmdir $targetdir; +remove_file $backuplog; DROP PROCEDURE populate_t1; DROP PROCEDURE display_aria_log_control; diff --git a/mysql-test/suite/mariabackup/partial_exclude.result b/mysql-test/suite/mariabackup/partial_exclude.result index 9f4c1042d9353..6e3ef79edd3f0 100644 --- a/mysql-test/suite/mariabackup/partial_exclude.result +++ b/mysql-test/suite/mariabackup/partial_exclude.result @@ -1,6 +1,6 @@ select @@ignore_db_dirs; @@ignore_db_dirs -db3,db4 +db3,db4,#clone,#ib_archive CREATE TABLE t1(i INT) ENGINE INNODB; INSERT INTO t1 VALUES(1); CREATE TABLE t2(i int) ENGINE INNODB; diff --git a/mysql-test/suite/perfschema/r/max_program_zero.result b/mysql-test/suite/perfschema/r/max_program_zero.result index 966c51505d205..3ca09c588d116 100644 --- a/mysql-test/suite/perfschema/r/max_program_zero.result +++ b/mysql-test/suite/perfschema/r/max_program_zero.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 1 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/ortho_iter.result b/mysql-test/suite/perfschema/r/ortho_iter.result index f86e36a2f9aea..a2e886c68df53 100644 --- a/mysql-test/suite/perfschema/r/ortho_iter.result +++ b/mysql-test/suite/perfschema/r/ortho_iter.result @@ -250,8 +250,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/privilege_table_io.result b/mysql-test/suite/perfschema/r/privilege_table_io.result index 2428f33f5dc56..9bb6ce24ce37d 100644 --- a/mysql-test/suite/perfschema/r/privilege_table_io.result +++ b/mysql-test/suite/perfschema/r/privilege_table_io.result @@ -56,8 +56,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_idle.result b/mysql-test/suite/perfschema/r/start_server_disable_idle.result index b892b249cc290..83f852a3698bf 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_idle.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_idle.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_stages.result b/mysql-test/suite/perfschema/r/start_server_disable_stages.result index 5e8f7b0b217d1..28dce707ed37a 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_stages.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_stages.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_statements.result b/mysql-test/suite/perfschema/r/start_server_disable_statements.result index 671d6c57898c1..c9d18e405f6fa 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_statements.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_statements.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_transactions.result b/mysql-test/suite/perfschema/r/start_server_disable_transactions.result index a4a98eb990925..cc1d3bc665325 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_transactions.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_transactions.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_disable_waits.result b/mysql-test/suite/perfschema/r/start_server_disable_waits.result index 8303ebfc76776..7ad8f0e5d6afb 100644 --- a/mysql-test/suite/perfschema/r/start_server_disable_waits.result +++ b/mysql-test/suite/perfschema/r/start_server_disable_waits.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_innodb.result b/mysql-test/suite/perfschema/r/start_server_innodb.result index 855a1f9b9e4c8..de09b210d3c38 100644 --- a/mysql-test/suite/perfschema/r/start_server_innodb.result +++ b/mysql-test/suite/perfschema/r/start_server_innodb.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_low_index.result b/mysql-test/suite/perfschema/r/start_server_low_index.result index 9e8be5f503393..a933c73b52f62 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_index.result +++ b/mysql-test/suite/perfschema/r/start_server_low_index.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_low_table_lock.result b/mysql-test/suite/perfschema/r/start_server_low_table_lock.result index 6ef2adbb3fd54..62d9c034d0d66 100644 --- a/mysql-test/suite/perfschema/r/start_server_low_table_lock.result +++ b/mysql-test/suite/perfschema/r/start_server_low_table_lock.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_account.result b/mysql-test/suite/perfschema/r/start_server_no_account.result index 940122e4c3eda..65efdba2a2eab 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_account.result +++ b/mysql-test/suite/perfschema/r/start_server_no_account.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_cond_class.result b/mysql-test/suite/perfschema/r/start_server_no_cond_class.result index 29ce17ef6e9e4..627a61037bd6f 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_cond_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_cond_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result b/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result index d84ac46d5db17..31904a1a81d5c 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_cond_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_file_class.result b/mysql-test/suite/perfschema/r/start_server_no_file_class.result index bc69fdcd7c098..84b17b4a43323 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_file_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_file_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_file_inst.result b/mysql-test/suite/perfschema/r/start_server_no_file_inst.result index 2c1540f599340..8519aae2cba1b 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_file_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_file_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_host.result b/mysql-test/suite/perfschema/r/start_server_no_host.result index d11f464b257ac..e969306378e4b 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_host.result +++ b/mysql-test/suite/perfschema/r/start_server_no_host.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_index.result b/mysql-test/suite/perfschema/r/start_server_no_index.result index e1441ceaf420d..56f024fe5ed00 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_index.result +++ b/mysql-test/suite/perfschema/r/start_server_no_index.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mdl.result b/mysql-test/suite/perfschema/r/start_server_no_mdl.result index f157c5c760ff8..a817f377e5431 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mdl.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mdl.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_memory_class.result b/mysql-test/suite/perfschema/r/start_server_no_memory_class.result index 650b94b2ddefe..65952ef6ff733 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_memory_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_memory_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result b/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result index 5fa48755fc4ce..f4f46d50117f9 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mutex_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result b/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result index e1333ac1ca08b..af1e8fedf69ba 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_mutex_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result b/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result index ee09e668d0cf6..4f9e44e2897ac 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result +++ b/mysql-test/suite/perfschema/r/start_server_no_prepared_stmts_instances.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result b/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result index 4bba60b0a0812..caf027daf4c71 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_rwlock_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result b/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result index 1806dea6a01ba..32887f59f8e15 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_rwlock_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 0 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result b/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result index 8755836d2c0ee..36adc76572fa1 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result +++ b/mysql-test/suite/perfschema/r/start_server_no_setup_actors.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result b/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result index cbf9ff102bd96..1debe9e86b8b6 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result +++ b/mysql-test/suite/perfschema/r/start_server_no_setup_objects.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_socket_class.result b/mysql-test/suite/perfschema/r/start_server_no_socket_class.result index 51eea13f61745..9969a4b880d0a 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_socket_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_socket_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 0 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result b/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result index 9377f695e258d..482757bb0a6e0 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_socket_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 0 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stage_class.result b/mysql-test/suite/perfschema/r/start_server_no_stage_class.result index 1dda39dc79e92..0d98a781e03eb 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stage_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stage_class.result @@ -129,7 +129,7 @@ performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 performance_schema_max_stage_classes 0 -performance_schema_max_statement_classes 227 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stages_history.result b/mysql-test/suite/perfschema/r/start_server_no_stages_history.result index ddebf586e86d0..4a97414a18098 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stages_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stages_history.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result index cd603ed318d0c..a7b453abd3cdb 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_stages_history_long.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statement_class.result b/mysql-test/suite/perfschema/r/start_server_no_statement_class.result index b1881c2183e46..607c27641c3c7 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statement_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statement_class.result @@ -128,7 +128,7 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 +performance_schema_max_stage_classes 175 performance_schema_max_statement_classes 0 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statements_history.result b/mysql-test/suite/perfschema/r/start_server_no_statements_history.result index c05e228f6fe92..c611479c3ba9e 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statements_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statements_history.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result index bcdf344691bf8..f0d71551da205 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_statements_history_long.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result b/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result index d7aa1ad2b7a57..573c507ccc7fa 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_hdl.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 0 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_inst.result b/mysql-test/suite/perfschema/r/start_server_no_table_inst.result index 761bd11937b10..e068e9271aa8d 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 0 diff --git a/mysql-test/suite/perfschema/r/start_server_no_table_lock.result b/mysql-test/suite/perfschema/r/start_server_no_table_lock.result index 7892e958859f0..5c380192182bc 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_table_lock.result +++ b/mysql-test/suite/perfschema/r/start_server_no_table_lock.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_thread_class.result b/mysql-test/suite/perfschema/r/start_server_no_thread_class.result index 00218a63b2381..e696547dfeb60 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_thread_class.result +++ b/mysql-test/suite/perfschema/r/start_server_no_thread_class.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result b/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result index 3754fcd1d4c7d..851ff41836c6b 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result +++ b/mysql-test/suite/perfschema/r/start_server_no_thread_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result b/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result index 86fa44f3d01fd..449d7b904f867 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_transactions_history.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result index 2f1b2116cb9d0..e6109293fcc4f 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_transactions_history_long.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_user.result b/mysql-test/suite/perfschema/r/start_server_no_user.result index b8681e71c6aa7..3394acf25ff45 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_user.result +++ b/mysql-test/suite/perfschema/r/start_server_no_user.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_waits_history.result b/mysql-test/suite/perfschema/r/start_server_no_waits_history.result index a65575083e7ec..4e375dc616868 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_waits_history.result +++ b/mysql-test/suite/perfschema/r/start_server_no_waits_history.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result b/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result index 88e6f6b2c972a..803850884bfa9 100644 --- a/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result +++ b/mysql-test/suite/perfschema/r/start_server_no_waits_history_long.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_off.result b/mysql-test/suite/perfschema/r/start_server_off.result index 0226b299a80c7..4a2e0f74fb25f 100644 --- a/mysql-test/suite/perfschema/r/start_server_off.result +++ b/mysql-test/suite/perfschema/r/start_server_off.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_on.result b/mysql-test/suite/perfschema/r/start_server_on.result index 855a1f9b9e4c8..de09b210d3c38 100644 --- a/mysql-test/suite/perfschema/r/start_server_on.result +++ b/mysql-test/suite/perfschema/r/start_server_on.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/start_server_variables.result b/mysql-test/suite/perfschema/r/start_server_variables.result index 729c895930f6b..6d4c402640ea1 100644 --- a/mysql-test/suite/perfschema/r/start_server_variables.result +++ b/mysql-test/suite/perfschema/r/start_server_variables.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 @@ -176,7 +176,7 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 +performance_schema_max_stage_classes 175 performance_schema_max_statement_stack 10 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/perfschema/r/statement_program_lost_inst.result b/mysql-test/suite/perfschema/r/statement_program_lost_inst.result index 4cbfee44d7f28..2919c8f04d127 100644 --- a/mysql-test/suite/perfschema/r/statement_program_lost_inst.result +++ b/mysql-test/suite/perfschema/r/statement_program_lost_inst.result @@ -128,8 +128,8 @@ performance_schema_max_rwlock_instances 5000 performance_schema_max_socket_classes 10 performance_schema_max_socket_instances 1000 performance_schema_max_sql_text_length 1024 -performance_schema_max_stage_classes 160 -performance_schema_max_statement_classes 227 +performance_schema_max_stage_classes 175 +performance_schema_max_statement_classes 229 performance_schema_max_statement_stack 2 performance_schema_max_table_handles 1000 performance_schema_max_table_instances 500 diff --git a/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled.result b/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled.result index cf8ca76e21456..56899468cb7db 100644 --- a/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled.result +++ b/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled.result @@ -67,6 +67,9 @@ stage/innodb/alter table (log apply table) YES stage/innodb/alter table (merge sort) YES stage/innodb/alter table (read PK and internal sort) YES stage/innodb/buffer pool load YES +stage/innodb/clone (file copy) YES +stage/innodb/clone (page copy) YES +stage/innodb/clone (redo copy) YES statement/com/Binlog Dump YES statement/com/Bulk_execute YES statement/com/Change user YES @@ -162,6 +165,9 @@ stage/innodb/alter table (log apply table) YES stage/innodb/alter table (merge sort) YES stage/innodb/alter table (read PK and internal sort) YES stage/innodb/buffer pool load YES +stage/innodb/clone (file copy) YES +stage/innodb/clone (page copy) YES +stage/innodb/clone (redo copy) YES statement/com/Binlog Dump YES statement/com/Bulk_execute YES statement/com/Change user YES diff --git a/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled_instruments.result b/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled_instruments.result index b0aa5f6d35780..49d4d9a79901f 100644 --- a/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled_instruments.result +++ b/mysql-test/suite/sysschema/r/pr_ps_setup_show_disabled_instruments.result @@ -13,6 +13,9 @@ stage/innodb/alter table (log apply table) YES stage/innodb/alter table (merge sort) YES stage/innodb/alter table (read PK and internal sort) YES stage/innodb/buffer pool load YES +stage/innodb/clone (file copy) YES +stage/innodb/clone (page copy) YES +stage/innodb/clone (redo copy) YES statement/com/Binlog Dump YES statement/com/Bulk_execute YES statement/com/Change user YES diff --git a/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled.result b/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled.result index 92b11ddf31f57..6706dcfe1e53d 100644 --- a/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled.result +++ b/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled.result @@ -143,6 +143,9 @@ stage/innodb/alter table (log apply table) YES stage/innodb/alter table (merge sort) YES stage/innodb/alter table (read PK and internal sort) YES stage/innodb/buffer pool load YES +stage/innodb/clone (file copy) YES +stage/innodb/clone (page copy) YES +stage/innodb/clone (redo copy) YES statement/com/Binlog Dump YES statement/com/Bulk_execute YES statement/com/Change user YES @@ -320,6 +323,9 @@ stage/innodb/alter table (log apply table) YES stage/innodb/alter table (merge sort) YES stage/innodb/alter table (read PK and internal sort) YES stage/innodb/buffer pool load YES +stage/innodb/clone (file copy) YES +stage/innodb/clone (page copy) YES +stage/innodb/clone (redo copy) YES statement/com/Binlog Dump YES statement/com/Bulk_execute YES statement/com/Change user YES diff --git a/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled_instruments.result b/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled_instruments.result index 36399f0d0ccf7..29ac2d5f9a053 100644 --- a/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled_instruments.result +++ b/mysql-test/suite/sysschema/r/pr_ps_setup_show_enabled_instruments.result @@ -84,6 +84,9 @@ stage/innodb/alter table (log apply table) YES stage/innodb/alter table (merge sort) YES stage/innodb/alter table (read PK and internal sort) YES stage/innodb/buffer pool load YES +stage/innodb/clone (file copy) YES +stage/innodb/clone (page copy) YES +stage/innodb/clone (redo copy) YES statement/com/Binlog Dump YES statement/com/Bulk_execute YES statement/com/Change user YES diff --git a/plugin/clone/CMakeLists.txt b/plugin/clone/CMakeLists.txt new file mode 100644 index 0000000000000..a336073f38029 --- /dev/null +++ b/plugin/clone/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright (c) 2018, 2024, Oracle and/or its affiliates. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. +# +# This program is designed to work with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have either included with +# the program or referenced in the documentation. +# +# 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +ADD_DEFINITIONS(-DMYSQL_SERVER) +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) + +MYSQL_ADD_PLUGIN(clone + src/clone_plugin.cc + src/clone_client.cc + src/clone_server.cc + src/clone_status.cc + src/clone_local.cc + src/clone_os.cc + src/clone_hton.cc + src/clone_se.cc + MODULE_ONLY RECOMPILE_FOR_EMBEDDED + MODULE_OUTPUT_NAME "mariadb_clone") diff --git a/plugin/clone/include/clone.h b/plugin/clone/include/clone.h new file mode 100644 index 0000000000000..2f088c9a63f4c --- /dev/null +++ b/plugin/clone/include/clone.h @@ -0,0 +1,398 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone.h +Clone Plugin: Common objects and interfaces + +*/ + +#ifndef CLONE_H +#define CLONE_H + +#include "mysqld_error.h" +#include "clone_hton.h" + +#include "mysql/psi/mysql_memory.h" +#include +#include "mysql/psi/mysql_statement.h" +#include +#include "mysql/psi/mysql_thread.h" +#include "violite.h" +#include "mysql_com_server.h" + +/** Clone memory key for performance schema */ +extern PSI_memory_key clone_mem_key; + +/** Key for registering clone local worker threads */ +extern PSI_thread_key clone_local_thd_key; + +/** Key for registering clone client worker threads */ +extern PSI_thread_key clone_client_thd_key; + +/** Clone Local statement */ +extern PSI_statement_key clone_stmt_local_key; + +/** Clone Remote client statement */ +extern PSI_statement_key clone_stmt_client_key; + +/** Clone Remote server statement */ +extern PSI_statement_key clone_stmt_server_key; + +/** Size of intermediate buffer for transferring data from source +file to network or destination file. */ +extern uint clone_buffer_size; + +/** Clone system variable: Maximum IO bandwidth in MiB/sec */ +extern uint clone_max_io_bandwidth; + +#if 0 +/** Clone system variable: If clone should block concurrent DDL */ +extern my_bool clone_block_ddl; + +/** Clone system variable: timeout for DDL lock */ +extern uint clone_ddl_timeout; + +/** Clone system variable: If concurrency is automatically tuned */ +extern my_bool clone_autotune_concurrency; + +/** Clone system variable: Maximum concurrent threads */ +extern uint clone_max_concurrency; + +/** Clone system variable: Maximum network bandwidth in MiB/sec */ +extern uint clone_max_network_bandwidth; + +/** Clone system variable: If network compression is enabled */ +extern my_bool clone_enable_compression; + +/** Clone system variable: SSL private key */ +extern char *clone_client_ssl_private_key; + +/** Clone system variable: SSL Certificate */ +extern char *clone_client_ssl_certificate; + +/** Clone system variable: SSL Certificate authority */ +extern char *clone_client_ssl_certficate_authority; + +/** Clone system variable: time delay after removing data */ +extern uint clone_delay_after_data_drop; + +#endif +/** Number of storage engines supporting clone. */ +const uint MAX_CLONE_STORAGE_ENGINE = 16; + +/** Maximum number of restart attempts */ +const uint CLONE_MAX_RESTART = 100; + +/** Minimum block size of clone data. */ +const uint CLONE_MIN_BLOCK = 1024 * 1024; + +/** Minimum network packet. Safe margin for meta information */ +const uint CLONE_MIN_NET_BLOCK = 2 * CLONE_MIN_BLOCK; + +/** Initialize clone interfaces for clone SE. */ +void init_clone_storage_engine(); + +void deinit_clone_storage_engine(); + +/* Namespace for all clone data types */ +namespace myclone { + +/** Clone protocol oldest version */ +const uint32_t CLONE_PROTOCOL_VERSION_V1 = 0x0100; + +/** Send also SO names along with plugin name */ +const uint32_t CLONE_PROTOCOL_VERSION_V2 = 0x0101; + +/** Send more configurations required by recipient. */ +const uint32_t CLONE_PROTOCOL_VERSION_V3 = 0x0102; + +/** Clone protocol latest version */ +const uint32_t CLONE_PROTOCOL_VERSION = CLONE_PROTOCOL_VERSION_V3; + +/** Flag to indicate no backup lock for DDL. This is multiplexed with +clone_ddl_timeout and sent to donor server. */ +const uint32_t NO_BACKUP_LOCK_FLAG = 1ULL << 31; + +/** Clone protocol commands. Please bump the protocol version before adding +new command. */ +typedef enum Type_Cmmand_RPC : uchar { + /** Initialize clone and negotiate version */ + COM_INIT = 1, + + /** Attach to current on going clone operation */ + COM_ATTACH, + + /** Re-Initialize clone network error */ + COM_REINIT, + + /** Execute command to clone remote database */ + COM_EXECUTE, + + /** Send Error or ACK data to remote database */ + COM_ACK, + + /** Exit clone protocol */ + COM_EXIT, + + /** Limit value for clone RPC */ + COM_MAX +} Command_RPC; + +/** Clone protocol COM_EXECUTE sub-commands. Please bump the protocol version +before adding new command. */ +typedef enum Type_Sub_Command : uchar { + /** No Sub command */ + SUBCOM_NONE = 0, + + /** Execution concurrent to DML and DDL. */ + SUBCOM_EXEC_CONCURRENT = 1, + + /** Execution blocking non-transactional DML. */ + SUBCOM_EXEC_BLOCK_NT_DML, + + /** Execution blocking DDL. */ + SUBCOM_EXEC_BLOCK_DDL, + + /** Execution synchronized snapshot including binary log position and GTID */ + SUBCOM_EXEC_SNAPSHOT, + + /** Execution at end after snapshot is taken. */ + SUBCOM_EXEC_END, + + /** Limit value for clone sub command */ + SUBCOM_MAX +} Sub_Command; + +/** Clone protocol response. Please bump the protocol version before adding +new response. */ +typedef enum Type_Command_Response : uchar { + /** Remote Locators */ + COM_RES_LOCS = 1, + + /** Remote Data descriptor */ + COM_RES_DATA_DESC, + + /** Remote Data */ + COM_RES_DATA, + + /** Plugin */ + COM_RES_PLUGIN, + + /** Configuration */ + COM_RES_CONFIG, + + /** Character set collation */ + COM_RES_COLLATION, + + /** Plugin with shared object name : introduced in version 0x0101 */ + COM_RES_PLUGIN_V2, + + /** Additional configuration : introduced in version 0x0102 */ + COM_RES_CONFIG_V3, + + /** Mater has taken appropriate locks for current stage. + Workers can now enter. */ + COM_RES_LOCKED, + + /** End of response data */ + COM_RES_COMPLETE = 99, + + /** Error in remote server operation */ + COM_RES_ERROR = 100, + + /** Limit value for clone RPC response */ + COM_RES_MAX +} Command_Response; + +/** Clone protocol backup lock stages. */ +enum Type_Command_Stages : uchar { + /* Same as BACKUP STAGE START */ + START, + /* BACKUP STAGE FLUSH */ + FLUSH, + /* BACKUP STAGE BLOCK_DDL */ + BLOCK_DDL, + /* BACKUP STAGE BLOCK_COMMIT */ + BLOCK_COMMIT, + /* BACKUP STAGE END */ + END +}; + +using String_Key = std::string; +using String_Keys = std::vector; + +using Key_Value = std::pair; +using Key_Values = std::vector; + +/** We transfers data between storage handle and external data link. +Storage handle is always identified by a set of locators provided by +the Storage Engines. External data link could be of type buffer or file +in case of local clone and network socket in case of remote clone. */ +enum Data_Link_Type { + CLONE_HANDLE_SOCKET = 1, + CLONE_HANDLE_BUFFER, + CLONE_HANDLE_FILE +}; + +/** Data stored in buffer */ +struct Buffer { + /** Initialize buffer */ + void init() { + m_buffer = nullptr; + m_length = 0; + } + + /** Allocate or Re-Allocate buffer + @param[in] length length to allocate or extend to + @return error if allocation fails. */ + int allocate(size_t length) { + if (m_length >= length) { + assert(m_buffer != nullptr); + return (0); + } + + uchar *temp_ptr = nullptr; + + if (m_buffer == nullptr) { + temp_ptr = + static_cast(my_malloc(clone_mem_key, length, MYF(MY_WME))); + + } else { + temp_ptr = static_cast( + my_realloc(clone_mem_key, m_buffer, length, MYF(MY_WME))); + } + + if (temp_ptr == nullptr) { + my_error(ER_OUTOFMEMORY, MYF(0), length); + return (ER_OUTOFMEMORY); + } + + m_buffer = temp_ptr; + m_length = length; + + return (0); + } + + /** Free buffer */ + void free() { + my_free(m_buffer); + init(); + } + + /** Buffer pointer */ + uchar *m_buffer; + + /** Buffer length */ + size_t m_length; +}; + +/** Data stored in file */ +struct File { + /** Open file descriptor */ + Ha_clone_file m_file_desc; + + /** Data length */ + uint m_length; +}; + +/** External data link for transfer */ +struct Data_Link { + /** Get external handle type. + @return handle type */ + Data_Link_Type get_type() { return (m_type); } + + /** Get external handle of type buffer. Caller must ensure + correct handle type. + @return clone buffer */ + Buffer *get_buffer() { + assert(m_type == CLONE_HANDLE_BUFFER); + return (&m_buffer); + } + + /** Set external handle buffer. + @param[in] in_buf buffer pointer + @param[in] in_len buffer length */ + void set_buffer(uchar *in_buf, uint in_len) { + m_type = CLONE_HANDLE_BUFFER; + + m_buffer.m_buffer = in_buf; + m_buffer.m_length = in_len; + } + + /** Get external handle of type file. Caller must ensure + correct handle type. + @return clone file */ + File *get_file() { + assert(m_type == CLONE_HANDLE_FILE); + return (&m_file); + } + + /** Set external handle file. + @param[in] in_file file descriptor + @param[in] in_len data length */ + void set_file(Ha_clone_file in_file, uint in_len) { + m_type = CLONE_HANDLE_FILE; + + m_file.m_file_desc = in_file; + m_file.m_length = in_len; + } + + /** Set external handle socket. + @param[in] socket network socket */ + void set_socket(MYSQL_SOCKET socket) { + m_type = CLONE_HANDLE_SOCKET; + m_socket = socket; + } + + private: + /** external handle type */ + Data_Link_Type m_type; + + /** external handle data */ + union { + MYSQL_SOCKET m_socket; + Buffer m_buffer; + File m_file; + }; +}; + +/** Validate all local configuration parameters. +@param[in] thd current session THD +@return error code */ +int validate_local_params(THD *thd); + +/** Log error to server error log. +@param level log level +@param error error code +@param string error message */ +void LogPluginErr(enum loglevel level, int error, const char* string); + +/** Command name for a execution sub-command. +@param sub_com sub command +@return string describing the command. */ +const char *sub_command_str(Sub_Command sub_com); + +} // namespace myclone + +#endif /* CLONE_H */ diff --git a/plugin/clone/include/clone_client.h b/plugin/clone/include/clone_client.h new file mode 100644 index 0000000000000..857eb3335e002 --- /dev/null +++ b/plugin/clone/include/clone_client.h @@ -0,0 +1,946 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone_client.h +Clone Plugin: Client Interface + +*/ + +#ifndef CLONE_CLIENT_H +#define CLONE_CLIENT_H + +#include "clone.h" +#include "clone_hton.h" +#include "clone_status.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Namespace for all clone data types */ +namespace myclone +{ + +using Clock = std::chrono::steady_clock; +using Time_Point = std::chrono::time_point; + +using Time_Msec = std::chrono::milliseconds; +using Time_Sec = std::chrono::seconds; +using Time_Min = std::chrono::minutes; + +struct Thread_Info +{ + /** Default constructor */ + Thread_Info() = default; + + /** Copy constructor needed for std::vector. */ + Thread_Info(const Thread_Info &) { reset(); } /* purecov: inspected */ + + /** Reset transferred data bytes. */ + void reset() { + m_last_update = Clock::now(); + m_last_data_bytes = 0; + m_last_network_bytes = 0; + + m_data_bytes.store(0); + m_network_bytes.store(0); + } + + /** Update transferred data bytes. + @param[in] data_bytes data bytes transferred + @param[in] net_bytes network bytes transferred */ + void update(uint64_t data_bytes, uint64_t net_bytes) { + m_data_bytes.fetch_add(data_bytes); + m_network_bytes.fetch_add(net_bytes); + } + + /** Calculate the expected time for transfer based on target. + @param[in] current current number of transferred data bytes + @param[in] prev previous number of transferred data bytes + @param[in] target target data transfer rate in bytes per second + @return expected time in milliseconds. */ + uint64_t get_target_time(uint64_t current, uint64_t prev, uint64_t target); + + /** Check target transfer speed and throttle if needed. The thread sleeps + for appropriate time if the current transfer rate is more than target. + @param[in] data_target target data bytes transfer per second + @param[in] net_target target network bytes transfer per second */ + void throttle(uint64_t data_target, uint64_t net_target); + + /** Data transfer throttle interval */ + Time_Msec m_interval{100}; + + /** Current thread */ + std::thread m_thread; + + /** Last time information was updated. */ + Time_Point m_last_update; + + /** Data bytes at last update. */ + uint64_t m_last_data_bytes{}; + + /** Network bytes at last update. */ + uint64_t m_last_network_bytes{}; + + /** Total amount of data transferred. */ + std::atomic m_data_bytes; + + /** Total amount of network bytes transferred. The value differs + from data as we use compression in network layer. */ + std::atomic m_network_bytes; +}; + +/** Thread information vector. */ +using Thread_Vector = std::vector; + +/** Maximum size of history data */ +const size_t STAT_HISTORY_SIZE = 16; + +/** Auto tuning information for threads. */ +struct Thread_Tune_Auto +{ + /** Auto tuning state */ + enum class State { INIT, ACTIVE, DONE }; + + /** Reset to initial state. */ + void reset() { + m_prev_number = 0; + m_next_number = 0; + m_cur_number = 0; + m_prev_speed = 0; + m_last_step_speed = 0; + m_prev_history_index = 0; + m_state = State::INIT; + } + + /** Statistics history interval for tuning. */ + const uint64_t m_history_interval{5}; + + /** Number of threads to increase in each step. */ + const uint64_t m_step{4}; + + /* Previous number of threads. */ + uint32_t m_prev_number{}; + + /** Next target number of threads. */ + uint32_t m_next_number{}; + + /** Current number of threads. */ + uint32_t m_cur_number{}; + + /** Average data transfer MB/sec */ + uint64_t m_prev_speed{}; + + /** Average data transfer in last step MB/sec */ + uint64_t m_last_step_speed{}; + + /* Saved history index on last tuning. */ + uint64_t m_prev_history_index{}; + + /** Current tuning state. */ + State m_state{State::INIT}; +}; + +/** Client data transfer statistics. */ +class Client_Stat +{ + public: + /** Update statistics data. + @param[in] reset reset all previous history + @param[in] threads all concurrent thread information + @param[in] num_workers current number of worker threads */ + void update(bool reset, const Thread_Vector &threads, uint32_t num_workers); + + /** Tune total number of threads based on stat + @param[in] num_threads current number of active threads + @param[in] max_threads maximum number of threads + @return suggested number of threads. */ + uint32_t get_tuned_thread_number(uint32_t num_threads, uint32_t max_threads); + + /** Get target speed, in case user has specified limits. + @param[out] data_speed target data transfer in bytes/sec + @param[out] net_speed target network transfer in bytes/sec */ + void get_target(uint64_t &data_speed, uint64_t &net_speed) const { + data_speed = m_target_data_speed.load(); + net_speed = m_target_network_speed.load(); + } + + /** Initialize target speed read by all threads. Adjusted later based on + maximum bandwidth threads. Zero implies unlimited bandwidth. */ + void init_target() { + m_target_data_speed.store(0); + m_target_network_speed.store(0); + } + + /** Save finished byte stat when thread info is released. It is + used during clone restart after network failure. + @param[in] data_bytes data bytes to save + @param[in] net_bytes network bytes to save */ + void save_at_exit(uint64_t data_bytes, uint64_t net_bytes) { + m_finished_data_bytes += data_bytes; + m_finished_network_bytes += net_bytes; + } + + /** Finish automatic tuning for spawning threads. */ + void finish_tuning() { m_tune.m_state = Thread_Tune_Auto::State::DONE; } + + /** Reset history elements. + @param[in] init true, if called during initialization */ + void reset_history(bool init); + + private: + /** Calculate target for each task based on current performance. + @param[in] target_speed overall target speed in bytes per second + @param[in] current_speed overall current speed in bytes per second + @param[in] current_target current target for a task in bytes per second + @param[in] num_tasks number of clone tasks + @return target for a task in bytes per second. */ + uint64_t task_target(uint64_t target_speed, uint64_t current_speed, + uint64_t current_target, uint32_t num_tasks); + + /** Set target bandwidth for data and network per thread. + @param[in] num_workers current number of worker threads + @param[in] is_reset if called during stage reset + @param[in] data_speed current data speed in bytes per second + @param[in] net_speed current network speed in bytes per second */ + void set_target_bandwidth(uint32_t num_workers, bool is_reset, + uint64_t data_speed, uint64_t net_speed); + + /** @return true if bandwidth limit is already reached. */ + bool is_bandwidth_saturated(); + + /** @return true if tuning has improved performance. + @param[in] num_threads current number of threads */ + bool tune_has_improved(uint32_t num_threads); + + /* Set next target number of threads + @param[in] num_threads current number of threads + @param[in] max_threads maximum number of threads */ + void tune_set_target(uint32_t num_threads, uint32_t max_threads); + + private: + /** Statistics update interval - 1 sec*/ + const Time_Msec m_interval{1000}; + + /** Minimum data transfer rate per task - 1M */ + const uint64_t m_minimum_speed = 1048576; + + /* If stat elements are initialized. */ + bool m_initialized{false}; + + /** Starting point for clone data transfer. */ + Time_Point m_start_time; + + /** Last evaluation time */ + Time_Point m_eval_time; + + /** Data transferred at last evaluation time. */ + uint64_t m_eval_data_bytes{}; + + /** All data bytes transferred by threads already finished. */ + uint64_t m_finished_data_bytes{}; + + /** Network bytes transferred at last evaluation time. */ + uint64_t m_eval_network_bytes{}; + + /** All data bytes transferred by threads already finished. */ + uint64_t m_finished_network_bytes{}; + + /** Network speed history. */ + std::array m_network_speed_history{}; + + /** Data speed history. */ + std::array m_data_speed_history{}; + + /** Current index for history data. */ + size_t m_current_history_index{}; + + /** Target Network bytes to be transferred per thread per second. */ + std::atomic m_target_network_speed; + + /** Target data bytes to be transferred per thread per second. */ + std::atomic m_target_data_speed; + + /** Thread auto tuning state and information. */ + Thread_Tune_Auto m_tune; +}; + +class Exec_State +{ + public: + /** Wait till current state is greater or equal to passed state. It is used + by worker threads before starting work for certain execution state. The + state is set by master. Attach to the current state. + @param state state to wait for on input, attached state on output + @return error code. */ + int begin_worker(Sub_Command &state); + + /** Detach worker from current execution state. + @param state state to detach from, must match the current execution state + @return error code. */ + int end_worker(Sub_Command state); + + /** Wait for all workers to finish current state and set the new state. It is + used by master to drive state transition. + @param thd THD to check for interrupt + @param state state to set. + @return error code. */ + int switch_state(THD *thd, Sub_Command next_state); + + /** Update current state. Called after acquiring locks for a state. + @param sub_state execution state + @return true if successful */ + bool update_current_state(Sub_Command sub_state); + + private: + /** Protects the state and counters. */ + std::mutex m_mutex; + + /** Condition variable for workers to wait for a state to begin. */ + std::condition_variable m_wait_state; + + /** Condition variable for master to wait for workers to finish state. */ + std::condition_variable m_wait_count; + + /** Current execution state. Protected by m_mutex. */ + Sub_Command m_cur_state= SUBCOM_NONE; + + /** Next execution state. Protected by m_mutex. */ + Sub_Command m_next_state= SUBCOM_NONE; + + /** Worker count within a state. Protected by m_mutex. */ + uint32_t m_count_workers[static_cast(SUBCOM_MAX) + 1]= {0}; +}; + +/* Shared client information for multi threaded clone */ +struct Client_Share +{ + /** Construct clone client share. Initialize storage handle. + @param[in] host remote host IP address + @param[in] port remote server port + @param[in] user remote user name + @param[in] passwd remote user's password + @param[in] dir target data directory for clone + @param[in] mode client SSL mode */ + Client_Share(const char *host, const uint port, const char *user, + const char *passwd, const char *dir, int mode) + : m_host(host), + m_port(port), + m_user(user), + m_passwd(passwd), + m_data_dir(dir), + m_ssl_mode(mode), + m_max_concurrency(1), + m_protocol_version(CLONE_PROTOCOL_VERSION) + { + m_storage_vec.reserve(MAX_CLONE_STORAGE_ENGINE); + m_threads.resize(m_max_concurrency); + assert(m_max_concurrency > 0); + m_stat.init_target(); + } + + /** Remote host name */ + const char *m_host; + + /** Remote port */ + const uint32_t m_port; + + /** Remote user name */ + const char *m_user; + + /** Remote user password */ + const char *m_passwd; + + /** Cloned database directory */ + const char *m_data_dir; + + /** Client SSL mode */ + const int m_ssl_mode; + + /** Maximum number of concurrent threads for current operation. */ + const uint32_t m_max_concurrency; + + /** Negotiated protocol version */ + uint32_t m_protocol_version; + + /** Clone storage vector */ + Storage_Vector m_storage_vec; + + /** Thread vector for multi threaded clone. */ + Thread_Vector m_threads; + + /** Data transfer statistics. */ + Client_Stat m_stat; + + /** Execution State. */ + Exec_State m_state; +}; + +/** Auxiliary connection to send ACK */ +struct Client_Aux +{ + /** Initialize members */ + void reset() + { + m_buffer = nullptr; + m_buf_len = 0; + m_cur_index = 0; + m_error = 0; + } + + /** Clone remote client connection */ + MYSQL *m_conn; + + /** ACK descriptor buffer */ + const uchar *m_buffer; + + /** ACK descriptor length */ + size_t m_buf_len; + + /** Current SE index */ + uint m_cur_index; + + /** Saved error */ + int m_error; +}; + +struct Remote_Parameters +{ + /** Remote plugins */ + String_Keys m_plugins; + + /** Remote character sets with collation */ + String_Keys m_charsets; + + /** Remote configurations to validate */ + Key_Values m_configs; + + /** Remote configurations to use */ + Key_Values m_other_configs; + + /** Remote plugins with shared object name */ + Key_Values m_plugins_with_so; +}; + +/** For Remote Clone, "Clone Client" is created at recipient. It receives data +over network from remote "Clone Server" and applies to Storage Engines. */ +class Client +{ + public: + /** Construct clone client. Initialize external handle. + @param[in,out] thd server thread handle + @param[in] share shared client information + @param[in] index current thread index + @param[in] is_master if it is master thread */ + Client(THD *thd, Client_Share *share, uint32_t index, bool is_master); + + /** Destructor: Free the transfer buffer, if created. */ + ~Client(); + + /** Check if it is the master client object. + @return true if this is the master object */ + bool is_master() const { return (m_is_master); } + + /** @return maximum concurrency for current clone operation. */ + uint32_t get_max_concurrency() const { + assert(m_share->m_max_concurrency > 0); + return (m_share->m_max_concurrency); + } + + /** @return current thread information. */ + Thread_Info &get_thread_info() { + return (m_share->m_threads[m_thread_index]); + } + + /** Check if network error + @param[in] err error code + @param[in] protocol_error include protocol error + @return true if network error */ + static bool is_network_error(int err, bool protocol_error); + + /** Update statistics and tune threads + @param[in] is_reset reset statistics + @return tuned number of worker threads. */ + uint32_t update_stat(bool is_reset); + + /** Check transfer speed and throttle. */ + void check_and_throttle(); + + /** Get auxiliary connection information + @return auxiliary connection data */ + Client_Aux *get_aux() { return (&m_conn_aux); } + + /** Get Shared area for client tasks + @return shared client data */ + Client_Share *get_share() { return (m_share); } + + /** Get storage handle vector for data transfer. + @return storage handle vector */ + Storage_Vector &get_storage_vector() { return (m_share->m_storage_vec); } + + /** Get tasks for different SE + @return task vector */ + Task_Vector &get_task_vector() { return (m_tasks); } + + /** Get external handle for data transfer. This is file + or buffer for local clone and network socket to remote server + for remote clone. + @param[out] conn connection handle to remote server + @return external handle */ + Data_Link *get_data_link(MYSQL *&conn) { + conn = m_conn; + return (&m_ext_link); + } + + /** Get server thread handle + @return server thread */ + THD *get_thd() { return (m_server_thd); } + + /** Get target clone data directory + @return data directory */ + const char *get_data_dir() const { return (m_share->m_data_dir); } + + /** Get clone locator for a storage engine at specified index. + @param[in] index locator index + @param[out] loc_len locator length in bytes + @return storage locator */ + const uchar *get_locator(uint index, uint &loc_len) const { + assert(index < m_share->m_storage_vec.size()); + + loc_len = m_share->m_storage_vec[index].m_loc_len; + return (m_share->m_storage_vec[index].m_loc); + } + + /** Get aligned intermediate buffer for transferring data. Allocate, + when called for first time. + @param[in] len length of allocated buffer + @return allocated buffer pointer */ + uchar *get_aligned_buffer(uint32_t len); + + /** Limit total memory used for clone transfer buffer. + @param[in] buffer_size configured buffer size + @return actual buffer size to allocate. */ + uint32_t limit_buffer(uint32_t buffer_size); + + /** Limit spawning initial number of workers if data or network + bandwidth is small. + @param[in] num_workers planned number of workers to spawn + @return actual number of workers to be spawned. */ + uint32_t limit_workers(uint32_t num_workers); + + /* Spawn worker threads. + @param[in] num_workers number of worker threads + @param[in] func worker function */ + template + void spawn_workers(uint32_t num_workers, F func) { + /* Currently we don't reduce the number of threads. */ + if (!is_master() || num_workers <= m_num_active_workers) { + return; + } + auto &thread_vector = m_share->m_threads; + + /* Maximum number of workers are fixed. */ + if (num_workers + 1 > get_max_concurrency()) { + assert(false); /* purecov: inspected */ + return; + } + + while (m_num_active_workers < num_workers) { + ++m_num_active_workers; + auto &info = thread_vector[m_num_active_workers]; + info.reset(); + try { + info.m_thread = std::thread(func, m_share, m_num_active_workers); + } catch (...) { + /* purecov: begin deadcode */ + auto &stat = m_share->m_stat; + stat.finish_tuning(); + + char info_mesg[64]; + snprintf(info_mesg, sizeof(info_mesg), "Failed to spawn worker: %d", + m_num_active_workers); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + + --m_num_active_workers; + break; + /* purecov: end */ + } + } + } + + /** Wait for worker threads to finish. */ + void wait_for_workers(); + + /** Get data from remote server and create cloned database by + applying to storage engines. + @return error code */ + int clone(); + + /** Execute clone moving through all execution states. + @param cbk callback function for executing one state + @return error code */ + int execute(std::function cbk); + + /** Execute RPC clone command on remote server + @param[in] com RPC command ID + @param[in] sub Sub command ID + @param[in] use_aux use auxiliary connection + @return error if not successful */ + int remote_command(Command_RPC com, Sub_Command sub, bool use_aux); + + /** Begin state in PFS table. + @return error code. */ + int pfs_begin_state(); + + /** Change stage in PFS progress table. */ + void pfs_change_stage(uint64_t estimate); + + /** End state in PFS table. + @param[in] err_num error number + @param[in] err_mesg error message */ + void pfs_end_state(uint32_t err_num, const char *err_mesg); + + /** Copy PFS status data safely. + @param[out] pfs_data status data. */ + static void copy_pfs_data(Status_pfs::Data &pfs_data); + + /** Copy PFS progress data safely. + @param[out] pfs_data progress data. */ + static void copy_pfs_data(Progress_pfs::Data &pfs_data); + + /** Update data and network consumed. + @param[in] data data bytes transferred + @param[in] network network bytes transferred + @param[in] data_speed data transfer speed in bytes/sec + @param[in] net_speed network transfer speed in bytes/sec + @param[in] num_workers number of worker threads */ + static void update_pfs_data(uint64_t data, uint64_t network, + uint32_t data_speed, uint32_t net_speed, + uint32_t num_workers); + + /** Init PFS mutex for table. */ + static void init_pfs(); + + /** Destroy PFS mutex for table. */ + static void uninit_pfs(); + + private: + /** Connect to remote server + @param[in] is_restart restarting clone after network failure + @param[in] use_aux establish auxiliary connection + @return error code */ + int connect_remote(bool is_restart, bool use_aux); + + /** Begin a clone execution state. + @param thd THD to check for interrupt + @param sub_state execution state + @return error code */ + int exec_begin_state(THD *thd, Sub_Command &sub_state); + + /* End clone execution state. + @param sub_state execution state + @return error code */ + int exec_end_state(Sub_Command sub_state); + + /** Check if the state should skipped. Currently only master thread needs to + take the snapshot. + @param sub_state execution state + @return true if the state needs to be skipped */ + bool skip_state(Sub_Command sub_state) const + { + return (!is_master() && sub_state == SUBCOM_EXEC_SNAPSHOT); + } + + /** Initialize storage engine and command buffer. + @param[in] mode initialization mode + @param[out] cmd_len serialized command length + @return error if initialization fails. */ + int init_storage(enum Ha_clone_mode mode, size_t &cmd_len); + + /** Prepare command buffer for remote RPC + @param[in] com RPC command ID + @param[in] sub Sub command ID + @param[out] buf_len command buffer length + @return error if allocation fails */ + int prepare_command_buffer(Command_RPC com, Sub_Command sub, + size_t &buf_len); + + /** Serialize the buffer for COM_INIT + @param[out] buf_len length of serialized buffer */ + int serialize_init_cmd(size_t &buf_len); + + /** Serialize the buffer for COM_EXEC + @param[in] sub Sub command ID + @param[out] buf_len length of serialized buffer */ + int serialize_exec_cmd(Sub_Command sub, size_t &buf_len); + + /** Serialize the buffer for COM_ACK + @param[out] buf_len length of serialized buffer */ + int serialize_ack_cmd(size_t &buf_len); + + /** Receive and handle response from remote server + @param[in] com RPC command ID + @param[in] use_aux use auxiliary connection + @return error code */ + int receive_response(Command_RPC com, bool use_aux); + + /** Handle response packet from remote server + @param[in] packet data packet + @param[in] length length of the packet + @param[in] in_err skip if error has occurred + @param[in] skip_loc skip applying locator + @param[out] is_last true if last packet + @return error code */ + int handle_response(const uchar *packet, size_t length, int in_err, + bool skip_loc, bool &is_last); + + /** Handle error and check if needs to exit + @param[in] current_err error number + @param[in,out] first_error first error that has occurred + @param[in,out] first_error_time time for first error in + milliseconds + @return true if the caller needs to exit */ + bool handle_error(int current_err, int &first_error, + ulonglong &first_error_time); + + /** Validate all remote parameters. + @return error code */ + int validate_remote_params(); + + /** Check if plugin is installed. + @param[in] plugin_name plugin name + @return true iff installed. */ + bool plugin_is_installed(std::string &plugin_name); + + /** Check if plugin shared object can be loaded. + @param[in] so_name shared object name + @return true iff able to load. */ + bool plugin_is_loadable(std::string &so_name); + + /** Extract string from network buffer. + @param[in,out] packet network packet + @param[in,out] length packet length + @param[out] str extracted string + @return error code */ + int extract_string(const uchar *&packet, size_t &length, String_Key &str); + + /** Extract string from network buffer. + @param[in,out] packet network packet + @param[in,out] length packet length + @param[out] keyval extracted key value pair + @return error code */ + int extract_key_value(const uchar *&packet, size_t &length, + Key_Value &keyval); + + /** Extract and add plugin name from network packet. + @param[in] packet network packet + @param[in] length packet length + @return error code */ + int add_plugin(const uchar *packet, size_t length); + + /** Extract and add plugin and shared object name from network packet. + @param[in] packet network packet + @param[in] length packet length + @return error code */ + int add_plugin_with_so(const uchar *packet, size_t length); + + /** Extract and add charset name from network packet. + @param[in] packet network packet + @param[in] length packet length + @return error code */ + int add_charset(const uchar *packet, size_t length); + + /** Extract and add remote configuration from network packet. + @param[in] packet network packet + @param[in] length packet length + @param[in] other true if additional configuration + @return error code */ + int add_config(const uchar *packet, size_t length, bool other); + + /** Use additional configurations if sent by donor. */ + void use_other_configs(); + + /** Set locators returned by remote server + @param[in] buffer serialized locator information + @param[in] length length of serialized data + @return error code */ + int set_locators(const uchar *buffer, size_t length); + + /** Allow workers to proceed as locks are already acquired + @param[in] buffer serialized execution state + @param[in] length length of serialized data + @return error code */ + int set_locked(const uchar *buffer, size_t length); + + /** Apply descriptor returned by remote server + @param[in] buffer serialized data descriptor + @param[in] length length of serialized data + @return error code */ + int set_descriptor(const uchar *buffer, size_t length); + + /** Extract and set error mesg from remote server + @param[in] buffer Remote error buffer + @param[in] length length of error buffer + @return error code */ + int set_error(const uchar *buffer, size_t length); + + /** Suspends client thread for the specified time + @param[in] wait_time Time in seconds + @return error code */ + int wait(Time_Sec wait_time); + + /** Check if delay is requested from the user + @return error code */ + int delay_if_needed(); + + /** If PFS table and mutex is initialized. */ + static bool s_pfs_initialized; + + private: + /** Clone status table data. */ + static Status_pfs::Data s_status_data; + + /** Clone progress table data. */ + static Progress_pfs::Data s_progress_data; + + /** Clone table mutex to protect PFS table data. */ + static mysql_mutex_t s_table_mutex; + + /** Number of concurrent clone clients. */ + static uint32_t s_num_clones; + + /** Time out for connecting back to donor server after network failure. */ + static Time_Sec s_reconnect_timeout; + + /** Interval for attempting re-connect after failure. */ + static Time_Sec s_reconnect_interval; + + private: + /** Server thread object */ + THD *m_server_thd; + + /** Auxiliary client connection */ + Client_Aux m_conn_aux; + + /** Clone remote client connection */ + MYSQL *m_conn; + NET_SERVER m_conn_server_extn; + + /** Intermediate buffer for data copy when zero copy is not used. */ + Buffer m_copy_buff; + + /** Buffer holding data for RPC command */ + Buffer m_cmd_buff; + + /** Clone external handle. Data is transferred from + external handle(network) to storage handle. */ + Data_Link m_ext_link; + + /** If it is the master thread */ + bool m_is_master; + + /** Thread index for multi-threaded clone */ + uint32_t m_thread_index; + + /** Number of active worker tasks. */ + uint32_t m_num_active_workers; + + /** Task IDs for different SE */ + Task_Vector m_tasks; + + /** Storage is initialized */ + bool m_storage_initialized; + + /** Storage is active with locators set */ + bool m_storage_active; + + /** If backup lock is acquired */ + bool m_acquired_backup_lock; + + /** Remote parameters for validation. */ + Remote_Parameters m_parameters; + + /** Shared client information */ + Client_Share *m_share; +}; + +/** Clone client interface to handle callback from Storage Engine */ +class Client_Cbk : public Ha_clone_cbk +{ + public: + /** Construct Callback. Set clone client object. + @param[in] clone clone client object */ + Client_Cbk(Client *clone) : m_clone_client(clone) {} + + /** Get clone object + @return clone client object */ + Client *get_clone_client() const { return (m_clone_client); } + + /** Clone client file callback: Not used for client. + @param[in] from_file source file descriptor + @param[in] len data length + @return error code */ + int file_cbk(Ha_clone_file from_file, uint len) override; + + /** Clone client buffer callback: Not used for client. + @param[in] from_buffer source buffer + @param[in] buf_len data length + @return error code */ + int buffer_cbk(uchar *from_buffer, uint buf_len) override; + + /** Clone client apply callback: Copy data to storage + engine file from network. + @param[in] to_file destination file descriptor + @return error code */ + int apply_file_cbk(Ha_clone_file to_file) override; + + /** Clone client apply callback: Get data in buffer + @param[out] to_buffer data buffer + @param[out] len data length + @return error code */ + int apply_buffer_cbk(uchar *&to_buffer, uint &len) override; + + private: + /** Apply data to local file or buffer. + @param[in,out] to_file destination file + @param[in] apply_file copy data to file + @param[out] to_buffer data buffer + @param[out] to_len data length + @return error code */ + int apply_cbk(Ha_clone_file to_file, bool apply_file, uchar *&to_buffer, + uint &to_len); + + private: + /** Clone client object */ + Client *m_clone_client; +}; + +} // namespace myclone + +#endif /* CLONE_CLIENT_H */ diff --git a/plugin/clone/include/clone_hton.h b/plugin/clone/include/clone_hton.h new file mode 100644 index 0000000000000..a7d5e4103388f --- /dev/null +++ b/plugin/clone/include/clone_hton.h @@ -0,0 +1,143 @@ +/* Copyright (c) 2018, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone_hton.h +Clone Plugin: Interface with SE handlerton + +*/ + +#ifndef CLONE_HTON_H +#define CLONE_HTON_H + +#include + +#include "my_global.h" +#include "mysql/plugin.h" +#include "handler.h" +#include "sql_plugin.h" + +/* Namespace for all clone data types */ +namespace myclone { + +struct Locator { + /** Get buffer length for serilized locator. + @return serialized length */ + size_t serlialized_length() const + { + /* Add one byte for SE type */ + return (1 + sizeof(m_loc_len) + m_loc_len); + } + + /** Serialize the structure + @param[in,out] buffer allocated buffer for serialized data + @return serialized length */ + size_t serialize(uchar *buffer); + + /** Deserialize the buffer to structure. The structure elements + point within the buffer and the buffer should not be freed while + using the structure. + @param[in,out] buffer serialized locator buffer + @return serialized length */ + size_t deserialize(THD *thd, const uchar *buffer); + + /** SE handlerton for the locator */ + handlerton *m_hton; + + /** Locator for the clone operation */ + const uchar *m_loc; + + /** Locator length */ + uint32 m_loc_len; +}; + +using Storage_Vector = std::vector; + +using Task_Vector = std::vector; + +} /* namespace myclone */ + +using myclone::Storage_Vector; +using myclone::Task_Vector; + +/** Begin clone operation for all storage engines supporting clone +@param[in,out] thd server thread handle +@param[in,out] clone_loc_vec vector of locators from SEs +@param[out] task_vec vector of task identifiers +@param[in] clone_type clone type +@param[in] clone_mode clone begin mode +@return error code */ +int hton_clone_begin(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, Ha_clone_type clone_type, + Ha_clone_mode clone_mode); + +/** Clone copy for all storage engines supporting clone +@param[in,out] thd server thread handle +@param[in] clone_loc_vec vector of locators for SEs +@param[in] task_vec vector of task identifiers +@param[in] clone_stage clone execution stage +@param[in] clone_cbk clone callback +@return error code */ +int hton_clone_copy(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, Ha_clone_stage clone_stage, + Ha_clone_cbk *clone_cbk); + +/** Clone end for all storage engines supporting clone +@param[in,out] thd server thread handle +@param[in] clone_loc_vec vector of locators for SEs +@param[in] task_vec vector of task identifiers +@param[in] in_err error code when ending after error +@return error code */ +int hton_clone_end(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, int in_err); + +/** Begin Clone apply operation for all storage engines supporting clone +@param[in,out] thd server thread handle +@param[in] clone_data_dir target data directory +@param[in,out] clone_loc_vec vector of locators from SEs +@param[out] task_vec vector of task identifiers +@param[in] clone_mode clone begin mode +@return error code */ +int hton_clone_apply_begin(THD *thd, const char *clone_data_dir, + Storage_Vector &clone_loc_vec, Task_Vector &task_vec, + Ha_clone_mode clone_mode); + +/** Clone apply error for all storage engines supporting clone +@param[in,out] thd server thread handle +@param[in] clone_loc_vec vector of locators for SEs +@param[in] task_vec vector of task identifiers +@param[in] in_err error code when ending after error +@return error code */ +int hton_clone_apply_error(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, int in_err); + +/** Clone apply end for all storage engines supporting clone +@param[in,out] thd server thread handle +@param[in] clone_loc_vec vector of locators for SEs +@param[in] task_vec vector of task identifiers +@param[in] in_err error code when ending after error +@return error code */ +int hton_clone_apply_end(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, int in_err); + +#endif /* CLONE_HTON_H */ diff --git a/plugin/clone/include/clone_local.h b/plugin/clone/include/clone_local.h new file mode 100644 index 0000000000000..10fa2c2880537 --- /dev/null +++ b/plugin/clone/include/clone_local.h @@ -0,0 +1,161 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone_local.h +Clone Plugin: Local clone interface + +*/ + +#ifndef CLONE_LOCAL_H +#define CLONE_LOCAL_H + +#include "clone.h" +#include "clone_client.h" +#include "clone_hton.h" +#include "clone_server.h" + +/* Namespace for all clone data types */ +namespace myclone { +/** We create it for Local Clone. It retrieves data from Storage Engines +and applies to the cloned data directory. It uses embedded "Clone Client" +and "Clone Server" object to accomplish the job. "Clone Server" is used to +fetch the data from server which is returned via callbacks. "Clone Client" +handle applies the data to create a cloned database. */ +class Local { + public: + /** Construct clone local. Initialize "Clone Client" and + "Clone Server" objects. + @param[in,out] thd server thread handle + @param[in] server shared server handle + @param[in] share shared client information + @param[in] index index of current thread + @param[in] is_master true, if it is master thread */ + Local(THD *thd, Server *server, Client_Share *share, uint32_t index, + bool is_master); + + /** Get clone client for data transfer. + @return clone client handle */ + Client *get_client() { return (&m_clone_client); } + + /** Get clone server for data transfer. + @return clone server handle */ + Server *get_server() { return (m_clone_server); } + + /** Clone current database and update PFS. + @return error code */ + int clone(); + + /** Clone current database to the destination data directory. + @return error code */ + int clone_exec(); + + private: + /** "Clone Server" object to copy data */ + Server *m_clone_server; + + /** "Clone Client" object to apply data */ + Client m_clone_client; +}; + +/** Clone Local interface to handle callback from Storage Engines */ +class Local_Callback : public Ha_clone_cbk { + public: + /** Construct Callback. Set clone local object. + @param[in] clone clone local object */ + Local_Callback(Local *clone) : m_clone_local(clone), m_apply_data(false) {} + + /** Get clone client object + @return clone client */ + Client *get_clone_client() { return (m_clone_local->get_client()); } + + /** Get clone server object + @return clone server */ + Server *get_clone_server() { return (m_clone_local->get_server()); } + + /** Get external handle of "Clone Client" + @return client external handle */ + Data_Link *get_client_data_link() { + auto client = m_clone_local->get_client(); + MYSQL *conn; + + return (client->get_data_link(conn)); + } + + /** Clone local file callback: Set source file as external handle + for embedded "Clone Client" object and apply data using storage + engine interface. + @param[in] from_file source file descriptor + @param[in] len data length + @return error code */ + int file_cbk(Ha_clone_file from_file, uint len) override; + + /** Clone local buffer callback: Set source buffer as external handle + for embedded "Clone Client" object and apply data using storage + engine interface. + @param[in] from_buffer source buffer + @param[in] buf_len data length + @return error code */ + int buffer_cbk(uchar *from_buffer, uint buf_len) override; + + /** Clone local apply file callback: Copy data from "Clone Client" + external handle to storage engine file. + @param[in] to_file destination file + @return error code */ + int apply_file_cbk(Ha_clone_file to_file) override; + + /** Clone local apply callback: get data in buffer + @param[out] to_buffer data buffer + @param[out] len data length + @return error code */ + int apply_buffer_cbk(uchar *&to_buffer, uint &len) override; + + private: + /** Apply data using storage engine apply interface. + @return error code */ + int apply_data(); + + /** Acknowledge data transfer. + @return error code */ + int apply_ack(); + + /** Apply data to local file or buffer. + @param[in,out] to_file destination file + @param[in] apply_file copy data to file + @param[out] to_buffer data buffer + @param[out] to_len data length + @return error code */ + int apply_cbk(Ha_clone_file to_file, bool apply_file, uchar *&to_buffer, + uint &to_len); + + private: + /** Clone local object */ + Local *m_clone_local; + + /** Applying cloned data */ + bool m_apply_data; +}; + +} // namespace myclone + +#endif /* CLONE_LOCAL_H */ diff --git a/plugin/clone/include/clone_os.h b/plugin/clone/include/clone_os.h new file mode 100644 index 0000000000000..d7f63cb94fd23 --- /dev/null +++ b/plugin/clone/include/clone_os.h @@ -0,0 +1,139 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone_os.h +Clone Plugin: OS specific routines for IO and network + +*/ + +#ifndef CLONE_OS_H +#define CLONE_OS_H + +#include +#include "my_sys.h" +#include "mysqld_error.h" +#include "mysys_err.h" +#include "handler.h" + +/** Alignment required for direct IO */ +const int CLONE_OS_ALIGN = 4 * 1024; + +/** Default maximum concurrency for clone */ +const uint CLONE_DEF_CON = 16; + +/** Copy data from file to buffer +@param[in] from_file source file descriptor +@param[in,out] to_buffer buffer to copy data +@param[in] length buffer/data length +@param[in] src_name source file name +@return error code */ +int clone_os_copy_file_to_buf(Ha_clone_file from_file, uchar *to_buffer, + uint length, const char *src_name); + +/** Check zero copy support +@return true if supports zero copy */ +bool clone_os_supports_zero_copy(); + +/** Copy data from one file to another. File descriptors should be positioned +by caller. Uses sendfile on linux to avoid copy to user buffer. If sendfile +fails it switches to read/write similar to other platforms. +@param[in] from_file source file descriptor +@param[in] to_file destination file descriptor +@param[in] length length of data in bytes to copy +@param[in] buffer intermediate buffer for data transfer +@param[in] buff_len intermediate buffer length +@param[in] src_name source file name +@param[in] dest_name destination file name +@return error code */ +int clone_os_copy_file_to_file(Ha_clone_file from_file, Ha_clone_file to_file, + uint length, uchar *buffer, uint buff_len, + const char *src_name, const char *dest_name); + +/** Copy data from buffer to file. File descriptor should be positioned +by caller. +@param[in] from_buffer source buffer +@param[in] to_file destination file descriptor +@param[in] length length of data in bytes to copy +@param[in] dest_name destination file name +@return error code */ +int clone_os_copy_buf_to_file(uchar *from_buffer, Ha_clone_file to_file, + uint length, const char *dest_name); + +/** Send data from buffer to network. +@param[in] from_buffer source buffer +@param[in] length length of data in bytes to copy +@param[in] socket network socket +@param[in] src_name source file name +@return error code */ +int clone_os_send_from_buf(uchar *from_buffer, uint length, my_socket socket, + const char *src_name); + +/** Send data from file to network. File descriptor should be positioned +by caller. +@param[in] from_file source file descriptor +@param[in] length length of data in bytes to copy +@param[in] socket network socket +@param[in] src_name source file name +@return error code */ +int clone_os_send_from_file(Ha_clone_file from_file, uint length, + my_socket socket, const char *src_name); + +/** Receive data from network to buffer. +@param[in] to_buffer destination buffer +@param[in] length length of data in bytes to copy +@param[in] socket network socket +@param[in] dest_name destination file name +@return error code */ +int clone_os_recv_to_buf(uchar *to_buffer, uint length, my_socket socket, + const char *dest_name); + +/** Receive data from network to file. File descriptor should be positioned +by caller. +@param[in] to_file destination file descriptor +@param[in] length length of data in bytes to copy +@param[in] socket network socket +@param[in] dest_name destination file name +@return error code */ +int clone_os_recv_to_file(Ha_clone_file to_file, uint length, my_socket socket, + const char *dest_name); + +/** Check if a shared object is present and can be loaded. +@param[in] path shared object file name and path +@return true iff shared object could be loaded successfully. */ +bool clone_os_test_load(std::string &path); + +/** Align pointer to CLONE_OS_ALIGN[4k]. +@param[in] pointer unaligned input +@return aligned pointer */ +inline uchar *clone_os_align(uchar *pointer) { + auto pointer_numeric = reinterpret_cast(pointer); + auto align = static_cast(CLONE_OS_ALIGN - 1); + + auto aligned_ptr = + reinterpret_cast((pointer_numeric + align) & ~align); + + return (aligned_ptr); +} + +#endif /* CLONE_OS_H */ diff --git a/plugin/clone/include/clone_server.h b/plugin/clone/include/clone_server.h new file mode 100644 index 0000000000000..afc3c03d41a08 --- /dev/null +++ b/plugin/clone/include/clone_server.h @@ -0,0 +1,318 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone_server.h +Clone Plugin: Server interface + +*/ + +#ifndef CLONE_SERVER_H +#define CLONE_SERVER_H + +#include "clone.h" +#include "clone_hton.h" +#include "clone_os.h" + +/* Namespace for all clone data types */ +namespace myclone { +/** For Remote Clone, "Clone Server" is created at donor. It retrieves data +from Storage Engines and transfers over network to remote "Clone Client". */ +class Server { + public: + /** Construct clone server. Initialize storage and external handle + @param[in,out] thd server thread handle + @param[in] socket network socket to remote client */ + Server(THD *thd, MYSQL_SOCKET socket); + + /** Destructor: Free the transfer buffer, if created. */ + ~Server(); + + /** Get storage handle vector for data transfer. + @return storage handle vector */ + Storage_Vector &get_storage_vector() { return (m_storage_vec); } + + /** Get clone locator for a storage engine at specified index. + @param[in] index locator index + @param[out] loc_len locator length in bytes + @return storage locator */ + const uchar *get_locator(uint index, uint &loc_len) const { + assert(index < m_storage_vec.size()); + loc_len = m_storage_vec[index].m_loc_len; + return (m_storage_vec[index].m_loc); + } + + /** Get tasks for different SE + @return task vector */ + Task_Vector &get_task_vector() { return (m_tasks); } + + /** Get external handle for data transfer. This is the + network socket to remote client. + @return external handle */ + Data_Link *get_data_link() { return (&m_ext_link); } + + /** Get server thread handle + @return server thread */ + THD *get_thd() { return (m_server_thd); } + + /** Allocate and return buufer for data copy + @param[in] len buffer length + @return allocated pointer */ + uchar *alloc_copy_buffer(uint len) { + auto err = m_copy_buff.allocate(len); + + if (err != 0) { + return (nullptr); + + } else { + assert(m_copy_buff.m_length >= len); + return (m_copy_buff.m_buffer); + } + } + + /** Clone database and send data to remote client. + @return error code */ + int clone(); + + /** Get clone execution stage and acquire appropriate lock if requested. + @param[in] sub_cmd sub command for execution + @param[out] stage execution stage + @param[in] lock true if needs to lock. Only master task should ask for lock. + @return error code */ + int get_stage_and_lock(Sub_Command sub_cmd, Ha_clone_stage &stage, + bool lock); + + /** Send Binary log position and GTID if replication is enabled. + @return error code */ + int send_replication_state(); + + /** Send descriptor data to remote client + @param[in,out] hton SE handlerton + @param[in] secure validate secure connection + @param[in] loc_index current locator index + @param[in] desc_buf descriptor buffer + @param[in] desc_len buffer length + @return error code */ + int send_descriptor(handlerton *hton, bool secure, uint loc_index, + const uchar *desc_buf, uint desc_len); + + /** Send one string value. + @param[in] rcmd response command + @param[in] key_str string key + @param[in] val_str string value + @return error code */ + int send_key_value(Command_Response rcmd, String_Key &key_str, + String_Key &val_str); + + /** Send configurations. + @param[in] rcmd response command + @return error code */ + int send_configs(Command_Response rcmd); + + /** @return true iff need to send only plugin name for old clone version. */ + bool send_only_plugin_name() const { + return m_protocol_version < CLONE_PROTOCOL_VERSION_V2; + } + + /** @return true iff skip sending additional configurations. */ + bool skip_other_configs() const { + return m_protocol_version < CLONE_PROTOCOL_VERSION_V3; + } + + private: + /** Extract client ddl timeout and backup lock flag. + @param[in] client_timeout timeout value received from client */ + void set_client_timeout(uint32_t client_timeout) { + m_backup_lock = ((client_timeout & NO_BACKUP_LOCK_FLAG) == 0); + m_client_ddl_timeout = client_timeout & ~NO_BACKUP_LOCK_FLAG; + } + + /** @return true if clone needs to block concurrent DDL. */ + bool block_ddl() const { return (m_is_master && m_backup_lock); } + + /** Check if network error + @param[in] err error code + @return true if network error */ + static bool is_network_error(int err) { + if (err == ER_NET_ERROR_ON_WRITE || err == ER_NET_READ_ERROR || + err == ER_NET_WRITE_INTERRUPTED || err == ER_NET_READ_INTERRUPTED) + return true; + + /* Check for protocol error */ + if (err == ER_NET_PACKETS_OUT_OF_ORDER || err == ER_NET_UNCOMPRESS_ERROR || + err == ER_NET_PACKET_TOO_LARGE || err == ER_CLONE_PROTOCOL) + return true; + + return false; + } + + /** Send status back to client + @param[in] err error code + @return error code */ + int send_status(int err); + + /** Initialize storage engine using command buffer. + @param[in] mode clone start mode + @param[in] com_buf command buffer + @param[in] com_len command buffer length + @return error code */ + int init_storage(Ha_clone_mode mode, uchar *com_buf, size_t com_len); + + /** Parse command buffer and execute + @param[in] command command type + @param[in] com_buf buffer to parse + @param[in] com_len buffer length + @param[out] done true if all clone commands are done + @return error code */ + int parse_command_buffer(uchar command, uchar *com_buf, size_t com_len, + bool &done); + + /** Run one specific execution phase. + @param[in] sub_cmd Sub command ID + @return error code */ + int execute_phase(Sub_Command sub_cmd); + + /** Deserialize COM_INIT command buffer to extract version and locators + @param[in] init_buf INIT command buffer + @param[in] init_len buffer length + @return error code */ + int deserialize_init_buffer(const uchar *init_buf, size_t init_len); + + /** Deserialize COM_ACK command buffer to extract descriptor + @param[in] ack_buf ACK command buffer + @param[in] ack_len buffer length + @param[in,out] cbk callback object + @param[out] err_code remote error + @param[out] loc Locator object + @return error code */ + int deserialize_ack_buffer(const uchar *ack_buf, size_t ack_len, + Ha_clone_cbk *cbk, int &err_code, Locator *loc); + + /** Deserialize COM_EXECUTE command buffer to extract execution phase + @param[in] exec_buf Execute command buffer + @param[in] exec_len buffer length + @param[out] sub_cmd Execution sub command + @return error code */ + int deserialize_exec_buffer(const uchar *exec_buf, size_t exec_len, + Sub_Command &sub_cmd); + + /** Send back the locators + @return error code */ + int send_locators(); + + /** Send information the locks are taken for current state + @param[in] sub_cmd current execution state + @return error code */ + int send_locked(Sub_Command sub_cmd); + + /** Send mysql server parameters + @return error code */ + int send_params(); + + private: + /** Server thread object */ + THD *m_server_thd; + + /** If this is the master task */ + bool m_is_master; + + /** Intermediate buffer for data copy when zero copy is not used. */ + Buffer m_copy_buff; + + /** Buffer holding data for RPC response */ + Buffer m_res_buff; + + /** Clone external handle. Data is transferred from + storage handle to external handle(network). */ + Data_Link m_ext_link; + + /** Clone storage handle */ + Storage_Vector m_storage_vec; + + /** Task IDs for different SE */ + Task_Vector m_tasks; + + /** Storage vector is initialized */ + bool m_storage_initialized; + + /** PFS statement is initialized */ + bool m_pfs_initialized; + + /** If backup lock is acquired */ + bool m_acquired_backup_lock; + + /** Negotiated protocol version */ + uint32_t m_protocol_version; + + /** DDL timeout from client */ + uint32_t m_client_ddl_timeout; + + /** If backup lock should be acquired */ + bool m_backup_lock; +}; + +/** Clone server interface to handle callback from Storage Engine */ +class Server_Cbk : public Ha_clone_cbk { + public: + /** Construct Callback. Set clone server object. + @param[in] clone clone server object */ + Server_Cbk(Server *clone) : m_clone_server(clone) {} + + /** Get clone object + @return clone server object */ + Server *get_clone_server() const { return (m_clone_server); } + + /** Send descriptor data to remote client */ + int send_descriptor(); + + /** Clone server file callback: Send data from file to remote client + @param[in] from_file source file descriptor + @param[in] len data length + @return error code */ + int file_cbk(Ha_clone_file from_file, uint len) override; + + /** Clone server buffer callback: Send data from buffer to remote client + @param[in] from_buffer source buffer + @param[in] buf_len data length + @return error code */ + int buffer_cbk(uchar *from_buffer, uint buf_len) override; + + /** Clone server apply callback: Not used for server. + @param[in] to_file destination file descriptor + @return error code */ + int apply_file_cbk(Ha_clone_file to_file) override; + + /** Clone server apply callback: Not used for server. + @param[out] to_buffer data buffer + @param[out] len data length + @return error code */ + int apply_buffer_cbk(uchar *&to_buffer, uint &len) override; + + private: + /** Clone server object */ + Server *m_clone_server; +}; + +} // namespace myclone + +#endif /* CLONE_SERVER_H */ diff --git a/plugin/clone/include/clone_status.h b/plugin/clone/include/clone_status.h new file mode 100644 index 0000000000000..04c916be5361c --- /dev/null +++ b/plugin/clone/include/clone_status.h @@ -0,0 +1,487 @@ +/* Copyright (c) 2019, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/include/clone_status.h +Clone Plugin: Client Status Interface + +*/ + +#ifndef CLONE_STATUS_H +#define CLONE_STATUS_H + +#include +#include "clone.h" + +#define PFS_HA_ERR_END_OF_FILE HA_ERR_END_OF_FILE + +/* Namespace for all clone data types */ +namespace myclone { + +/** Log the current error message. +@param[in,out] thd current session THD +@param[in] is_client true, if called by client +@param[in] error error code +@param[in] message_start start error message string */ +void log_error(THD *thd, bool is_client, int32_t error, + const char *message_start); + +/** Abstract base class for clone PFS tables. */ +class Table_pfs +{ + public: + /** Constructor. + @param[in] num_rows total number of rows in table. */ + Table_pfs(uint32_t num_rows); + + /** Destructor. */ + virtual ~Table_pfs() = default; + + /** Read column at index of current row. Implementation + is specific to table. + @param[out] field column value + @param[in] index column position within row + @return error code. */ + // virtual int read_column_value(PSI_field *field, uint32_t index) = 0; + + /** Initialize the table. + @return plugin table error code. */ + virtual int rnd_init() = 0; + + /** Initialize position for table. + @param[in] id clone ID. */ + void init_position(uint32_t id) { + m_position = 0; + m_empty = (id == 0); + } + + /** Set cursor to next record. + @return plugin table error code. */ + int rnd_next() { + /* Table is empty. */ + if (is_empty()) { + return (PFS_HA_ERR_END_OF_FILE); + } + ++m_position; + if (m_position <= m_rows) { + return (0); + } + /* All rows are read. */ + assert(m_position == m_rows + 1); + return (PFS_HA_ERR_END_OF_FILE); + } + + /** Set cursor to current position: currently no op. + @return plugin table error code. */ + int rnd_pos() { + if (m_position > 0 && m_position <= m_rows) { + return (0); + } + return (PFS_HA_ERR_END_OF_FILE); + } + + /** Reset cursor position to beginning. */ + void reset_pos() { m_position = 0; } + + /** Close the table. */ + void close() { m_position = 0; } + + /* @return address of current position. PFS needs it to set + the position for proxy table. */ + uint32_t *get_position_address() { return (&m_position); } + + /** Acquire service handles and create proxy tables + @return false if successful. */ + static bool acquire_services(); + + /** Release service handles and delete proxy tables. */ + static void release_services(); + + /** Initialize all stage and state names. */ + static void init_state_names(); + + /** Clone States. */ + enum Clone_state : uint32_t { + STATE_NONE = 0, + STATE_STARTED, + STATE_SUCCESS, + STATE_FAILED, + NUM_STATES + }; + /** All clone states */ + static std::array s_state_names; + + /** Clone Stages. Keep in consecutive order as we use it as index. */ + enum Clone_stage : uint32_t { + STAGE_NONE = 0, + STAGE_CLEANUP = 1, + STAGE_FILE_COPY = 2, + STAGE_PAGE_COPY = 3, + STAGE_REDO_COPY = 4, + STAGE_FILE_SYNC = 5, + STAGE_RESTART = 6, + STAGE_RECOVERY = 7, + NUM_STAGES = 8 + }; + + /** All clone Stages. */ + static std::array s_stage_names; + + protected: + /** @return Current cursor position. */ + uint32_t get_position() const { return (m_position); } + + /** @return Proxy table share reference. */ + // PFS_engine_table_share_proxy *get_proxy_share() { return (&m_pfs_table); } + + /** @return true, if no data in table. */ + bool is_empty() const { return (m_empty); } + + private: + /** Create PFS proxy tables. + @return error code. */ + static int create_proxy_tables(); + + /** Drop PFS proxy tables. */ + static void drop_proxy_tables(); + + private: + /** Number of rows in table. */ + uint32_t m_rows; + + /** Current position of the cursor. */ + uint32_t m_position; + + /** If the table is empty. */ + bool m_empty; + + /** Proxy table defined in plugin to register callbacks with PFS. */ + // PFS_engine_table_share_proxy m_pfs_table; +}; + +const char g_local_string[] = "LOCAL INSTANCE"; + +class Status_pfs : public Table_pfs +{ + public: + /* Constructor. */ + Status_pfs(); + + /** Read column at specific index of current row. + @param[out] field column value + @param[in] index column position within row + @return error code. */ + // int read_column_value(PSI_field *field, uint32_t index) override; + + /** Initialize the table. + @return plugin table error code. */ + int rnd_init() override; + + /** Number of rows in status table. Currently we keep last clone status. */ + static const uint32_t S_NUM_ROWS = 1; + + /** POD for the progress data. */ + struct Data { + /** Read data from status file. */ + void read(); + + /** Extract and write recovery information. */ + void recover(); + + /** Write data to status file. + @param[in] write_error write error information. */ + void write(bool write_error); + + /* @return true, if destination is current database. */ + bool is_local() const { + return (0 == strncmp(&m_destination[0], &g_local_string[0], + sizeof(m_destination))); + } + + /** Set PFS table data while starting Clone operation. + @param[in] id clone ID + @param[in] thd session THD + @param[in] host clone source host + @param[in] port clone source port + @param[in] destination clone destination directory or host */ + void begin(uint32_t id, THD *thd, const char *host, uint32_t port, + const char *destination) { + m_id = id; + m_pid = static_cast(thd_get_thread_id(thd)); + /* Clone from local instance. */ + if (host == nullptr) { + strncpy(m_source, &g_local_string[0], sizeof(m_source) - 1); + } else { + snprintf(m_source, sizeof(m_source) - 1, "%s:%u", host, port); + } + /* Clone into local instance. */ + if (destination == nullptr) { + destination = &g_local_string[0]; + } + strncpy(m_destination, destination, sizeof(m_destination) - 1); + m_error_number = 0; + memset(m_error_mesg, 0, sizeof(m_error_mesg)); + m_binlog_pos = 0; + memset(m_binlog_file, 0, sizeof(m_binlog_file)); + m_gtid_string.clear(); + m_start_time = microsecond_interval_timer(); + m_end_time = 0; + m_state = STATE_STARTED; + write(false); + } + + /** Update PFS table data while ending clone operation. + @param[in] err_num error number + @param[in] err_mesg error message + @param[in] provisioning if we are provisioning current directory. */ + void end(uint32_t err_num, const char *err_mesg, bool provisioning) { + m_end_time = microsecond_interval_timer(); + if (err_num == 0) { + /* For provisioning, recovery stage is left. */ + if (!provisioning) { + m_state = Table_pfs::STATE_SUCCESS; + } + write(true); + return; + } + m_state = Table_pfs::STATE_FAILED; + m_error_number = err_num; + strncpy(m_error_mesg, err_mesg, sizeof(m_error_mesg) - 1); + write(true); + } + + /** Update source binlog position consistent with cloned data. + @param[in] binlog_file binary log file name + @param[in] position binary log offset within file */ + void update_binlog_position(const char *binlog_file, uint64_t position) { + m_binlog_pos = position; + strncpy(m_binlog_file, binlog_file, sizeof(m_binlog_file) - 1); + } + + /** Length of variable length character columns. */ + static const size_t S_VAR_COL_LENGTH = 512; + + /** Current State. */ + Clone_state m_state{STATE_NONE}; + + /** Clone error number. */ + uint32_t m_error_number{}; + + /** Unique identifier in current instance. */ + uint32_t m_id{}; + + /** Process List ID. */ + uint32_t m_pid{}; + + /** Clone start time. */ + uint64_t m_start_time{}; + + /** Clone end time. */ + uint64_t m_end_time{}; + + /* Source binary log position. */ + uint64_t m_binlog_pos{}; + + /** Clone source. */ + char m_source[S_VAR_COL_LENGTH]{}; + + /** Clone destination. */ + char m_destination[S_VAR_COL_LENGTH]{}; + + /** Clone error message. */ + char m_error_mesg[S_VAR_COL_LENGTH]{}; + + /** Source binary log file name. */ + char m_binlog_file[S_VAR_COL_LENGTH]{}; + + /** Clone GTID set */ + std::string m_gtid_string; + }; + + private: + /** Current status data. */ + Data m_data; +}; + +class Progress_pfs : public Table_pfs +{ + public: + /* Constructor. */ + Progress_pfs(); + + /** Read column at specific index of current row. + @param[out] field column value + @param[in] index column position within row + @return error code. */ + // int read_column_value(PSI_field *field, uint32_t index) override; + + /** Initialize the table. + @return plugin table error code. */ + int rnd_init() override; + + /** Number of rows in progress table. Therea is one row for each stage. */ + static const uint32_t S_NUM_ROWS = NUM_STAGES - 1; + + /** POD for the progress data. */ + struct Data { + /** Read data from progress file. */ + void read(); + + /** Write data to progress file. + @@param[in] data_dir data directory for write. */ + void write(const char *data_dir); + + /** Get next stage from current. + @param[in,out] stage current/next stage. */ + void next_stage(Clone_stage &stage) { + auto next_num = static_cast(stage) + 1; + auto max_num = static_cast(NUM_STAGES); + if (next_num >= max_num) { + stage = STAGE_NONE; + return; + } + stage = static_cast(next_num); + } + + /** Initialize PFS stage. + @@param[in] data_dir data directory for write. */ + void init_stage(const char *data_dir) { + m_id = 0; + m_current_stage = STAGE_NONE; + + /* Clean current stage information. */ + m_data_speed = 0; + m_network_speed = 0; + + /* Clean all stage specific information. */ + next_stage(m_current_stage); + while (m_current_stage != STAGE_NONE) { + /* State */ + m_states[m_current_stage] = STATE_NONE; + m_threads[m_current_stage] = 0; + /* Time */ + m_start_time[m_current_stage] = 0; + m_end_time[m_current_stage] = 0; + /* Estimates */ + m_estimate[m_current_stage] = 0; + m_complete[m_current_stage] = 0; + m_network[m_current_stage] = 0; + + next_stage(m_current_stage); + } + write(data_dir); + } + + /** Set PFS table data while starting Clone a stage. + @param[in] id clone ID + @@param[in] data_dir data directory for write. + @param[in] threads current number of concurrent threads + @param[in] estimate estimated data bytes for stage */ + void begin_stage(uint32_t id, const char *data_dir, uint64_t threads, + uint64_t estimate) { + next_stage(m_current_stage); + if (m_current_stage == STAGE_NONE) { + assert(false); /* purecov: inspected */ + return; + } + m_states[m_current_stage] = STATE_STARTED; + m_id = id; + m_threads[m_current_stage] = static_cast(threads); + + /* Set time at beginning. */ + m_start_time[m_current_stage] = microsecond_interval_timer(); + m_end_time[m_current_stage] = 0; + + /* Reset progress data at the beginning of stage. */ + m_estimate[m_current_stage] = estimate; + m_complete[m_current_stage] = 0; + m_network[m_current_stage] = 0; + m_data_speed = 0; + m_network_speed = 0; + write(data_dir); + } + + /** Set PFS table data while ending a Clone stage. + @@param[in] data_dir data directory for write. */ + void end_stage(bool failed, const char *data_dir) { + m_end_time[m_current_stage] = microsecond_interval_timer(); + m_states[m_current_stage] = failed ? STATE_FAILED : STATE_SUCCESS; + write(data_dir); + } + + /** Update data and network consumed. + @param[in] data data bytes transferred + @param[in] network network bytes transferred + @param[in] data_speed data transfer speed in bytes/sec + @param[in] net_speed network transfer speed in bytes/sec + @param[in] num_workers number of worker threads */ + void update_data(uint64_t data, uint64_t network, uint32_t data_speed, + uint32_t net_speed, uint32_t num_workers) { + m_complete[m_current_stage] += data; + m_network[m_current_stage] += network; + m_data_speed = data_speed; + m_network_speed = net_speed; + m_threads[m_current_stage] = num_workers + 1; + } + + /** Current progress stage. */ + Clone_stage m_current_stage{STAGE_NONE}; + + /** State information for all stages. */ + Clone_state m_states[NUM_STAGES]; + + /** Unique identifier in current instance. */ + uint32_t m_id{}; + + /** Current data transfer rate. */ + uint32_t m_data_speed{}; + + /** Current network transfer rate. */ + uint32_t m_network_speed{}; + + /** Number of active threads. */ + uint32_t m_threads[NUM_STAGES]{}; + + /** Stage start time. */ + uint64_t m_start_time[NUM_STAGES]{}; + + /** Stage end time. */ + uint64_t m_end_time[NUM_STAGES]{}; + + /** Estimated bytes for all stages. */ + uint64_t m_estimate[NUM_STAGES]{}; + + /** Completed bytes for all stages. */ + uint64_t m_complete[NUM_STAGES]{}; + + /** Completed network bytes for all stages. */ + uint64_t m_network[NUM_STAGES]{}; + }; + + private: + /** Current progress data. */ + Data m_data; +}; +} // namespace myclone + +#endif /* CLONE_STATUS_H */ diff --git a/plugin/clone/src/clone_client.cc b/plugin/clone/src/clone_client.cc new file mode 100644 index 0000000000000..f29f86c658aac --- /dev/null +++ b/plugin/clone/src/clone_client.cc @@ -0,0 +1,2267 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_client.cc +Clone Plugin: Client implementation + +*/ +#include + +#include "clone_client.h" +#include "clone_os.h" + +#include "my_byteorder.h" +// #include "sql/sql_thd_internal_api.h" +#include "sql_string.h" +#include +#include + +/* Namespace for all clone data types */ +namespace myclone +{ + +/** Default timeout is 300 seconds */ +Time_Sec Client::s_reconnect_timeout{300}; + +/** Minimum interval is 5 seconds. The actual value could be more based on +MySQL connect_timeout configuration. */ +Time_Sec Client::s_reconnect_interval{5}; + +/** Start concurrent clone operation +@param[in] share shared client information +@param[in] index current thread index */ +static void clone_client(Client_Share *share, uint32_t index) +{ + /* Create a session statement and set PFS keys */ + auto thd= clone_start_statement(nullptr, clone_client_thd_key, + PSI_NOT_INSTRUMENTED, "clone_client"); + Client clone_inst(thd, share, index, false); + + clone_inst.clone(); + + /* Drop the statement and session */ + clone_finish_statement(thd); +} + +uint64_t Thread_Info::get_target_time(uint64_t current, uint64_t prev, + uint64_t target) +{ + /* Target zero implies no throttling. */ + if (target == 0) + return target; + + assert(current >= prev); + auto bytes= current - prev; + auto target_time_ms= (bytes * 1000) / target; + return target_time_ms; +} + +void Thread_Info::throttle(uint64_t data_target, uint64_t net_target) +{ + auto cur_time= Clock::now(); + auto duration= + std::chrono::duration_cast(cur_time - m_last_update); + + /* Check only at specific intervals. */ + if (duration < m_interval) + return; + + /* Find the amount of time we should have taken based on the targets. */ + auto d_tm= get_target_time(m_data_bytes, m_last_data_bytes, data_target); + auto n_tm = + get_target_time(m_network_bytes, m_last_network_bytes, net_target); + auto target_ms= std::max(d_tm, n_tm); + + auto duration_ms= static_cast(duration.count()); + + /* Sleep for the remaining time to throttle clone data transfer. */ + if (target_ms > duration_ms) + { + auto sleep_ms= target_ms - duration_ms; + + /* Don't sleep for more than 1 second so that we don't get into + network timeout and can respond to abort/shutdown request. */ + if (sleep_ms > 1000) + { + sleep_ms= 1000; + /* Lower check interval as we need to sleep more. This way + we sleep more frequently. */ + m_interval= m_interval / 2; + } + Time_Msec sleep_time(sleep_ms); + std::this_thread::sleep_for(sleep_time); + } + else + /* Reset interval back to default 100ms. */ + m_interval= Time_Msec{100}; + + m_last_data_bytes= m_data_bytes; + m_last_network_bytes= m_network_bytes; + m_last_update= Clock::now(); +} + +void Client_Stat::update(bool reset, const Thread_Vector &threads, + uint32_t num_workers) +{ + /* Ignore reset requests when stat is not initialized. */ + if (!m_initialized && reset) + return; + + auto cur_time= Clock::now(); + + /* Start time is set at first call. */ + if (!m_initialized) + { + m_start_time= cur_time; + m_initialized= true; + reset_history(true); + set_target_bandwidth(num_workers, true, 0, 0); + return; + } + + auto duration_ms = + std::chrono::duration_cast(cur_time - m_eval_time); + if (duration_ms < m_interval && !reset) + return; + + m_eval_time= cur_time; + uint64_t value_ms= duration_ms.count(); + + uint64_t data_bytes= m_finished_data_bytes; + uint64_t net_bytes= m_finished_network_bytes; + + /* Evaluate total data and network bytes transferred till now. */ + for (uint32_t index= 0; index <= num_workers; ++index) + { + auto &thread_info= threads[index]; + data_bytes+= thread_info.m_data_bytes; + net_bytes+= thread_info.m_network_bytes; + } + + /* Evaluate the transfer speed from last evaluation time. */ + auto cur_index= m_current_history_index % STAT_HISTORY_SIZE; + ++m_current_history_index; + + uint64_t data_speed{}; + uint64_t net_speed{}; + if (value_ms == 0) + /* We might be too early here during reset. */ + assert(reset); + else + { + /* Update PFS in bytes per second. */ + assert(data_bytes >= m_eval_data_bytes); + auto data_inc= data_bytes - m_eval_data_bytes; + + assert(net_bytes >= m_eval_network_bytes); + auto net_inc= net_bytes - m_eval_network_bytes; + + data_speed= (data_inc * 1000) / value_ms; + net_speed= (net_inc * 1000) / value_ms; + Client::update_pfs_data(data_inc, net_inc, + static_cast(data_speed), + static_cast(net_speed), + num_workers); + } + + /* Calculate speed in MiB per second. */ + auto data_speed_mib= data_speed / (1024 * 1024); + auto net_speed_mib= net_speed / (1024 * 1024); + + m_data_speed_history[cur_index]= data_speed_mib; + m_network_speed_history[cur_index]= net_speed_mib; + + /* Set currently evaluated data. */ + m_eval_data_bytes= data_bytes; + m_eval_network_bytes= net_bytes; + + if (reset) + { + /* Convert to Mebibytes (MiB) */ + auto total_data_mb= data_bytes / (1024 * 1024); + auto total_net_mb= net_bytes / (1024 * 1024); + + /* Find and log cumulative data transfer rate. */ + duration_ms = + std::chrono::duration_cast(cur_time - m_start_time); + value_ms= duration_ms.count(); + + data_speed_mib= (value_ms == 0) ? 0 : (total_data_mb * 1000) / value_ms; + net_speed_mib= (value_ms == 0) ? 0 : (total_net_mb * 1000) / value_ms; + + /* Log current speed. */ + const size_t MESG_SZ= 128; + char info_mesg[MESG_SZ]; + + snprintf(info_mesg, MESG_SZ, + "Total Data: %" PRIu64 " MiB @ %" PRIu64 + " MiB/sec, Network: %" PRIu64 " MiB @ %" PRIu64 " MiB/sec", + total_data_mb, data_speed_mib, total_net_mb, net_speed_mib); + + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + reset_history(false); + } + + if (num_workers != 0) + /* Set targets for all tasks. */ + set_target_bandwidth(num_workers, reset, data_speed, net_speed); +} + +uint64_t Client_Stat::task_target(uint64_t target_speed, uint64_t current_speed, + uint64_t current_target, uint32_t num_tasks) +{ + assert(num_tasks > 0); + + /* Zero is special value indicating unlimited bandwidth. */ + if (target_speed == 0) + return 0; + + /* Estimate number of active tasks based on current performance. If target is + not set yet, start by assuming all active thread. */ + auto active_tasks = + (current_target == 0) ? num_tasks : (current_speed / current_target); + + /* Keep the value within current boundary. */ + if (active_tasks == 0) + active_tasks= 1; + else if (active_tasks > num_tasks) + active_tasks= num_tasks; + + auto task_target= target_speed / active_tasks; + + /* Don't set anything lower than a minimum threshold. Protection against + bad configuration asking too many threads and very less bandwidth. */ + if (task_target < m_minimum_speed) + task_target= m_minimum_speed; + + return task_target; +} + +void Client_Stat::set_target_bandwidth(uint32_t num_workers, bool is_reset, + uint64_t data_speed, uint64_t net_speed) +{ + uint64_t data_target= clone_max_io_bandwidth * 1024 * 1024; + if (!is_reset) + data_target= + task_target(data_target, data_speed, m_target_data_speed, num_workers); + m_target_data_speed.store(data_target); +#if 0 + ++num_workers; + + uint64_t data_target= clone_max_io_bandwidth * 1024 * 1024; + uint64_t net_target= clone_max_network_bandwidth * 1024 * 1024; + + if (!is_reset) + { + data_target= + task_target(data_target, data_speed, m_target_data_speed, num_workers); + + net_target= + task_target(net_target, net_speed, m_target_network_speed, num_workers); + } + + m_target_data_speed.store(data_target); + m_target_network_speed.store(net_target); +#endif +} + +void Client_Stat::reset_history(bool init) +{ + m_data_speed_history.fill(0); + m_network_speed_history.fill(0); + m_current_history_index= 0; + + /* Set evaluation results during initialization. */ + if (init) + { + m_eval_data_bytes= 0; + m_finished_data_bytes= 0; + m_eval_network_bytes= 0; + m_finished_network_bytes= 0; + m_eval_time= Clock::now(); + } + + /** Reset auto tuning information. */ + m_tune.reset(); +} + +bool Client_Stat::is_bandwidth_saturated() +{ + return false; +#if 0 + if (m_current_history_index == 0) + return false; + + auto last_index= (m_current_history_index - 1) % STAT_HISTORY_SIZE; + + /* Check if data speed is close to the limit. We consider it saturated if 90% + is reached and stop spawning more threads. */ + auto data_speed= m_data_speed_history[last_index]; + auto max_io= clone_max_io_bandwidth; + + /* Zero implies no limit on bandwidth. */ + if (max_io != 0) + { + max_io *= 0.9; + if (data_speed > max_io) + return true; + } + /* Check if network speed is close to the limit. */ + auto net_speed= m_network_speed_history[last_index]; + auto max_net= clone_max_network_bandwidth; + + if (max_net != 0) + { + max_net *= 0.9; + if (net_speed > max_net) + return true; + } + return false; +#endif +} + +bool Client_Stat::tune_has_improved(uint32_t num_threads) +{ + const size_t MESG_SZ= 128; + char info_mesg[MESG_SZ]; + if (m_tune.m_cur_number != num_threads) + { + snprintf(info_mesg, MESG_SZ, "Tune stop, current: %u expected: %u", + num_threads, m_tune.m_cur_number); + return false; + } + auto gap_target= m_tune.m_next_number - m_tune.m_prev_number; + auto gap_current= m_tune.m_cur_number - m_tune.m_prev_number; + + assert(m_current_history_index > 0); + auto last_index= (m_current_history_index - 1) % STAT_HISTORY_SIZE; + double data_speed= static_cast(m_data_speed_history[last_index]); + double target_speed= static_cast(m_tune.m_prev_speed); + + if (gap_target == gap_current) + /* We continue if at least 25% improvement after reaching target. */ + target_speed *= 1.25; + else if (gap_current >= gap_target / 2) + /* We continue if at least 10% improvement after reaching 50% target. */ + target_speed *= 1.10; + else if (gap_current >= gap_target / 4) + /* We continue if at least 5% improvement after reaching 25% target. */ + target_speed *= 1.05; + else + { + /* we continue only if hasn't degraded for other steps. */ + target_speed= static_cast(m_tune.m_last_step_speed); + target_speed *= 0.95; + } + + if (data_speed < target_speed) + snprintf(info_mesg, MESG_SZ, + "Tune stop, Data: %f MiB/sec, Target: %f MiB/sec.", + data_speed, target_speed); + else + snprintf(info_mesg, MESG_SZ, + "Tune continue, Data: %f MiB/sec, Target: %f MiB/sec", + data_speed, target_speed); + + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + + return (data_speed >= target_speed); +} + +void Client_Stat::tune_set_target(uint32_t num_threads, uint32_t max_threads) +{ + /* Note the current speed of data transfer. */ + assert(m_current_history_index > 0); + auto last_index= (m_current_history_index - 1) % STAT_HISTORY_SIZE; + auto current_speed= m_data_speed_history[last_index]; + /* Check if we have reached current target. */ + if (m_tune.m_cur_number == m_tune.m_next_number) + { + /* Set new target */ + m_tune.m_prev_number= num_threads; + m_tune.m_cur_number= num_threads; + /* Next target is double the number of threads. */ + m_tune.m_next_number= 2 * num_threads; + /* Should not exceed maximum concurrency. */ + if (m_tune.m_next_number > max_threads) + m_tune.m_next_number= max_threads; + m_tune.m_prev_speed= current_speed; + } + assert(m_tune.m_cur_number == num_threads); + /* We attempt to improve performance by adding more threads in steps. */ + m_tune.m_cur_number+= static_cast(m_tune.m_step); + m_tune.m_last_step_speed= current_speed; + + /* Should not set more than the current target. */ + if (m_tune.m_cur_number > m_tune.m_next_number) + m_tune.m_cur_number= m_tune.m_next_number; + + const size_t MESG_SZ= 128; + char info_mesg[MESG_SZ]; + snprintf(info_mesg, MESG_SZ, + "Tune Threads from: %u to: %d prev: %d target: %d", + num_threads, m_tune.m_cur_number, m_tune.m_prev_number, + m_tune.m_next_number); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); +} + +uint32_t Client_Stat::get_tuned_thread_number(uint32_t num_threads, + uint32_t max_threads) +{ + if (m_current_history_index < m_tune.m_prev_history_index) + { + assert(false); /* purecov: inspected */ + return num_threads; + } + auto interval= m_current_history_index - m_tune.m_prev_history_index; + /* Wait till some history is populated. */ + if (interval < m_tune.m_history_interval) + return num_threads; + + m_tune.m_prev_history_index= m_current_history_index; + /* No more tuning once we have reached DONE state. */ + if (m_tune.m_state == Thread_Tune_Auto::State::DONE) + return num_threads; + + /** Cannot go beyond maximum number of threads. */ + if (num_threads >= max_threads || is_bandwidth_saturated()) + { + finish_tuning(); + return num_threads; + } + /* Go to active state and set target. */ + if (m_tune.m_state == Thread_Tune_Auto::State::INIT) + { + tune_set_target(num_threads, max_threads); + m_tune.m_state= Thread_Tune_Auto::State::ACTIVE; + return m_tune.m_cur_number; + } + assert(m_tune.m_state == Thread_Tune_Auto::State::ACTIVE); + /* If it failed to improve speed, give up tuning. */ + if (!tune_has_improved(num_threads)) + { + finish_tuning(); + return m_tune.m_cur_number; + } + /* Successfully increased threads with good improvement. */ + tune_set_target(num_threads, max_threads); + + return m_tune.m_cur_number; +} + +inline void net_server_ext_init(NET_SERVER *ns) +{ + ns->m_user_data= nullptr; + ns->m_before_header= nullptr; + ns->m_after_header= nullptr; +} + +Client::Client(THD *thd, Client_Share *share, uint32_t index, bool is_master) + : m_server_thd(thd), + m_conn(), + m_is_master(is_master), + m_thread_index(index), + m_num_active_workers(), + m_storage_initialized(false), + m_storage_active(false), + m_acquired_backup_lock(false), + m_share(share) +{ + m_ext_link.set_socket(MYSQL_INVALID_SOCKET); + + /* Master must be at index zero */ + if (is_master) + { + assert(index == 0); + m_thread_index= 0; + } + + /* Reset thread statistics. */ + auto &info= get_thread_info(); + info.reset(); + + m_tasks.reserve(MAX_CLONE_STORAGE_ENGINE); + + m_copy_buff.init(); + m_cmd_buff.init(); + + m_conn_aux.m_conn= nullptr; + m_conn_aux.reset(); + + net_server_ext_init(&m_conn_server_extn); +} + +Client::~Client() +{ + assert(!m_storage_initialized); + assert(!m_storage_active); + m_copy_buff.free(); + m_cmd_buff.free(); +} + +bool Client::is_network_error(int err, bool protocol_error) +{ + /* Check for read/write error */ + if (err == ER_NET_ERROR_ON_WRITE || err == ER_NET_READ_ERROR || + err == ER_NET_WRITE_INTERRUPTED || err == ER_NET_READ_INTERRUPTED) + // err == ER_NET_WAIT_ERROR) { + return true; + + /* Check for protocol/shutdown error */ + if (err == ER_NET_PACKETS_OUT_OF_ORDER || err == ER_NET_UNCOMPRESS_ERROR || + err == ER_NET_PACKET_TOO_LARGE || err == ER_QUERY_INTERRUPTED || + err == ER_CLONE_PROTOCOL) + return protocol_error; + + return false; +} + +uint32_t Client::update_stat(bool is_reset) +{ + /* Statistics is updated by master task. */ + if (!is_master()) + return m_num_active_workers; + + auto &stat= m_share->m_stat; + stat.update(is_reset, m_share->m_threads, m_num_active_workers); + + if (is_reset) + return m_num_active_workers; + + /** Check if we need to spawn more threads. */ + auto num_threads= stat.get_tuned_thread_number(m_num_active_workers + 1, + get_max_concurrency()); + assert(num_threads >= 1); + return (num_threads - 1); +} + +void Client::check_and_throttle() +{ + uint64_t data_speed{}; + uint64_t net_speed{}; + + auto &stat= m_share->m_stat; + stat.get_target(data_speed, net_speed); + + auto &info= get_thread_info(); + info.throttle(data_speed, net_speed); +} + +uchar *Client::get_aligned_buffer(uint32_t len) +{ + auto err= m_copy_buff.allocate(len + CLONE_OS_ALIGN); + + if (err != 0) + return nullptr; + + /* Align buffer to CLONE_OS_ALIGN[4K] for O_DIRECT */ + auto buf_ptr= clone_os_align(m_copy_buff.m_buffer); + return buf_ptr; +} + +void Client::wait_for_workers() +{ + if (!is_master()) + { + assert(m_num_active_workers == 0); + return; + } + /* Wait for concurrent worker tasks to finish. */ + auto &thread_vector= m_share->m_threads; + assert(thread_vector.size() > m_num_active_workers); + auto &stat= m_share->m_stat; + + while (m_num_active_workers > 0) + { + auto &info= thread_vector[m_num_active_workers]; + info.m_thread.join(); + + /* Save all transferred bytes by the thread. */ + stat.save_at_exit(info.m_data_bytes, info.m_network_bytes); + info.reset(); + + --m_num_active_workers; + } + /* Save all transferred bytes by master thread. */ + auto &info= get_thread_info(); + stat.save_at_exit(info.m_data_bytes, info.m_network_bytes); + info.reset(); + + /* Reset stat and tuning information for next cycle after restart. */ + stat.reset_history(false); +} + +int Client::pfs_begin_state() +{ + if (!is_master()) + return 0; + + mysql_mutex_lock(&s_table_mutex); + /* Check and exit if concurrent clone in progress. */ + if (s_num_clones != 0) + { + mysql_mutex_unlock(&s_table_mutex); + assert(s_num_clones == 1); + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(0), 1); + return ER_CLONE_TOO_MANY_CONCURRENT_CLONES; + } + s_num_clones= 1; + s_status_data.begin(1, get_thd(), m_share->m_host, m_share->m_port, + get_data_dir()); + s_progress_data.init_stage(get_data_dir()); + mysql_mutex_unlock(&s_table_mutex); + + return 0; +} + +void Client::pfs_change_stage(uint64_t estimate) +{ + if (!is_master()) + return; + + mysql_mutex_lock(&s_table_mutex); + s_progress_data.end_stage(false, get_data_dir()); + s_progress_data.begin_stage(1, get_data_dir(), m_num_active_workers + 1, + estimate); + s_status_data.write(false); + mysql_mutex_unlock(&s_table_mutex); +} + +void Client::pfs_end_state(uint32_t err_num, const char *err_mesg) +{ + if (!is_master()) + return; + + mysql_mutex_lock(&s_table_mutex); + assert(s_num_clones == 1); + + const bool provisioning= (get_data_dir() == nullptr); + const bool failed= (err_num != 0); + + /* In case provisioning is successful, clone operation is still + in progress and will continue after restart. */ + if (!provisioning || failed) + s_num_clones= 0; + + s_progress_data.end_stage(failed, get_data_dir()); + s_status_data.end(err_num, err_mesg, provisioning); + mysql_mutex_unlock(&s_table_mutex); +} + +void Client::copy_pfs_data(Status_pfs::Data &pfs_data) +{ + mysql_mutex_lock(&s_table_mutex); + /* If clone operation is started skip recovering previous data. */ + if (s_num_clones == 0) + s_status_data.recover(); + + pfs_data= s_status_data; + mysql_mutex_unlock(&s_table_mutex); +} + +void Client::copy_pfs_data(Progress_pfs::Data &pfs_data) +{ + mysql_mutex_lock(&s_table_mutex); + pfs_data= s_progress_data; + mysql_mutex_unlock(&s_table_mutex); +} + +void Client::update_pfs_data(uint64_t data, uint64_t network, + uint32_t data_speed, uint32_t net_speed, + uint32_t num_workers) +{ + s_progress_data.update_data(data, network, data_speed, net_speed, + num_workers); +} + +bool Client::s_pfs_initialized= false; + +void Client::init_pfs() +{ + mysql_mutex_init(PSI_NOT_INSTRUMENTED, &s_table_mutex, MY_MUTEX_INIT_FAST); + /* Recover PFS data. */ + s_progress_data.read(); + s_status_data.read(); + s_pfs_initialized= true; +} + +void Client::uninit_pfs() +{ + if (s_pfs_initialized) + mysql_mutex_destroy(&s_table_mutex); + + s_pfs_initialized= false; +} + +uint32_t Client::limit_buffer(uint32_t buffer_size) +{ + /* Limit total buffer size to 128 M */ + const uint32_t max_buffer_size= 128 * 1024 * 1024; + auto num_tasks= get_max_concurrency(); + + auto limit= max_buffer_size / num_tasks; + + if (buffer_size > limit) + buffer_size= limit; + + return buffer_size; +} + +uint32_t Client::limit_workers(uint32_t num_workers) +{ + return 0; +} +#if 0 + /* Adjust if network bandwidth is limited. Currently 64 M + minimum per task is ensured before spawning task. */ + if (clone_max_network_bandwidth > 0) + { + /* Zero is also valid result for the limit. Workers are over and above + the master task. So, anything less than 64M would mean no workers to + spawn immediately. */ + const uint32_t limit= clone_max_network_bandwidth / 64; + if (num_workers > limit) + num_workers= limit; + } + + /* Adjust if data bandwidth is limited. Currently 64 M + minimum per task is ensured before spawning task. */ + if (clone_max_io_bandwidth > 0) + { + /* Zero is also valid result for the limit. Workers are over and above + the master task. So, anything less than 64M would mean no workers to + spawn immediately. */ + const uint32_t limit= clone_max_io_bandwidth / 64; + if (num_workers > limit) + num_workers= limit; + } + return num_workers; +} +#endif + +const char *sub_command_str(Sub_Command sub_com) +{ + const char *ret= ""; + switch(sub_com) + { + case SUBCOM_NONE: + ret= "COM_EXECUTE: SUBCOM_NONE"; + break; + case SUBCOM_EXEC_CONCURRENT: + ret= "COM_EXECUTE: SUBCOM_EXEC_CONCURRENT"; + break; + case SUBCOM_EXEC_BLOCK_NT_DML: + ret= "COM_EXECUTE: SUBCOM_EXEC_BLOCK_NT_DML"; + break; + case SUBCOM_EXEC_BLOCK_DDL: + ret= "COM_EXECUTE: SUBCOM_EXEC_BLOCK_DDL"; + break; + case SUBCOM_EXEC_SNAPSHOT: + ret= "COM_EXECUTE: SUBCOM_EXEC_SNAPSHOT"; + break; + case SUBCOM_EXEC_END: + return "COM_EXECUTE: SUBCOM_EXEC_END"; + break; + case SUBCOM_MAX: + assert(false); + ret= "COM_EXECUTE: SUBCOM_MAX"; + break; + } + return ret; +} + +int Exec_State::begin_worker(Sub_Command &state) +{ + std::unique_lock lock(m_mutex); + auto cond_fn= [&] + { + state= std::max(state, m_next_state); + return (m_cur_state >= state); + }; + + /* Wait till the execution state has reached desired state. */ + m_wait_state.wait(lock, cond_fn); + state= m_cur_state; + auto cur_index= static_cast(m_cur_state); + ++m_count_workers[cur_index]; + + return 0; +} + +int Exec_State::end_worker(Sub_Command state) +{ + std::unique_lock lock(m_mutex); + auto cur_index= static_cast(state); + + assert(state == m_cur_state || m_cur_state == SUBCOM_MAX); + assert(m_count_workers[cur_index] > 0); + + if (!(--m_count_workers[cur_index])) + m_wait_count.notify_one(); + + return 0; +} + +int Exec_State::switch_state(THD *thd, Sub_Command next_state) +{ + std::unique_lock lock(m_mutex); + assert(m_cur_state <= next_state); + auto cur_index= static_cast(m_cur_state); + + auto cond_fn= [&] + { + return (m_count_workers[cur_index] == 0); + }; + int err= 0; + bool result= false; + Time_Sec m_interval{1}; + + while (!result && next_state != SUBCOM_MAX) + { + result= m_wait_count.wait_for(lock, m_interval, cond_fn); + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err= ER_QUERY_INTERRUPTED; + break; + } + } + + if (err || next_state == SUBCOM_MAX) + { + m_next_state= SUBCOM_MAX; + m_cur_state= SUBCOM_MAX; + lock.unlock(); + m_wait_state.notify_all(); + } + else + /* The current state would be set later after appropriate locks are + acquired handled by COM_RES_LOCKED. */ + m_next_state= next_state; + + return err; +} + +bool Exec_State::update_current_state(Sub_Command sub_state) +{ + bool success= true; + std::unique_lock lock(m_mutex); + + assert(m_cur_state <= m_next_state); + assert(sub_state == m_next_state); + + if (sub_state != m_next_state) + { + m_next_state= SUBCOM_MAX; + m_cur_state= SUBCOM_MAX; + success= false; + } + else if (m_cur_state != m_next_state) + { + m_cur_state= m_next_state; + lock.unlock(); + m_wait_state.notify_all(); + } + return success; +} + +int Client::exec_begin_state(THD *thd, Sub_Command &sub_state) +{ + auto &exec_state= m_share->m_state; + if (is_master()) + return exec_state.switch_state(thd, sub_state); + + return exec_state.begin_worker(sub_state); +} + +int Client::exec_end_state(Sub_Command sub_state) +{ + auto &exec_state= m_share->m_state; + if (is_master()) + { + exec_state.update_current_state(sub_state); + return 0; + } + return exec_state.end_worker(sub_state); +} + +int Client::execute(std::function cbk) +{ + int err= 0; + auto cur_st= static_cast(SUBCOM_EXEC_CONCURRENT); + auto end_st= static_cast(SUBCOM_EXEC_END); + + for (;;) + { + auto sub_state= static_cast(cur_st); + auto local_err= exec_begin_state(get_thd(), sub_state); + + /* We might have attached to a different state. */ + cur_st= static_cast(sub_state); + + if (cur_st > end_st) + break; + assert(cur_st <= end_st); + + if (!local_err && !skip_state(sub_state)) + local_err= cbk(sub_state); + + exec_end_state(sub_state); + /* In case of any error, jump to the final state. */ + if (local_err) + { + assert(!err); + cur_st= end_st; + err= local_err; + } + ++cur_st; + } + return err; +} + +int Client::clone() +{ + const size_t MESG_SZ= 128; + bool restart= false; + uint restart_count= 0; + char info_mesg[MESG_SZ]; + + // auto num_workers= get_max_concurrency() - 1; + + /* Begin PFS state if no concurrent clone in progress. */ + auto err= pfs_begin_state(); + if (err != 0) + return err; + + do + { + ++restart_count; + + err= connect_remote(restart, false); + log_error(get_thd(), true, err, "Task Connect"); + + if (err != 0) + break; + + /* Make another auxiliary connection for ACK */ + err= connect_remote(restart, true); + + if (is_master()) + log_error(get_thd(), true, err, "Source ACK Connect"); + + if (err != 0) + { + assert(is_master()); + assert(m_conn == nullptr); + assert(m_conn_aux.m_conn == nullptr); + if (restart) + continue; + + break; + } + + auto rpc_com= is_master() ? COM_INIT : COM_ATTACH; + + if (restart) + { + assert(is_master()); + rpc_com= COM_REINIT; + } + + /* Negotiate clone protocol and SE versions */ + err= remote_command(rpc_com, SUBCOM_NONE, false); + + /* Delay clone after dropping database if requested */ + + if (err == 0 && rpc_com == COM_INIT) + { + assert(is_master()); + err= delay_if_needed(); + } + snprintf( + info_mesg, MESG_SZ, "Command %s", + is_master() ? (restart ? "COM_REINIT" : "COM_INIT") : "COM_ATTACH"); + log_error(get_thd(), true, err, &info_mesg[0]); + + /* Execute clone command */ + if (err == 0) + { +#if 0 + /* Spawn concurrent client tasks if auto tuning is off. */ + if (!clone_autotune_concurrency) + { + /* Limit number of workers based on other configurations. */ + auto to_spawn= limit_workers(num_workers); + using namespace std::placeholders; + auto func= std::bind(clone_client, _1, _2); + spawn_workers(to_spawn, func); + } +#endif + auto exec_callback= [&](Sub_Command sub_state) + { + int err= remote_command(COM_EXECUTE, sub_state, false); + snprintf(info_mesg, MESG_SZ, "Command COM_EXECUTE: %s", + sub_command_str(sub_state)); + log_error(get_thd(), true, err, &info_mesg[0]); + return err; + }; + + err= execute(exec_callback); + + /* For network error master would attempt + to restart clone. */ + if (is_master() && is_network_error(err, false)) + { + log_error(get_thd(), true, err, "Source Network issue"); + restart= true; + } + } + + /* Break from restart loop if not network error */ + if (restart && !is_network_error(err, false)) + { + log_error(get_thd(), true, err, "Source break restart loop"); + restart= false; + } + + /* Disconnect auxiliary connection for master */ + if (is_master()) + { + /* Ask other end to exit clone protocol */ + auto err2= remote_command(COM_EXIT, SUBCOM_NONE, true); + log_error(get_thd(), true, err2, "Source ACK COM_EXIT"); + + /* If clone is interrupted, ask the remote to exit. */ + if (err2 == 0 && err == ER_QUERY_INTERRUPTED) + { + err2= clone_kill(m_conn_aux.m_conn, m_conn); + log_error(get_thd(), true, err2, "Source Interrupt"); + } + + /* if COM_EXIT is unsuccessful, abort the connection */ + auto abort_net_aux= (err2 != 0); + + clone_disconnect(nullptr, m_conn_aux.m_conn, abort_net_aux, false); + m_conn_aux.m_conn= nullptr; + + snprintf(info_mesg, MESG_SZ, "Source ACK Disconnect : abort: %s", + abort_net_aux ? "true" : "false"); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + } + + /* For network, protocol error and shutdown, abort network connection. */ + auto abort_net= is_network_error(err, true); + + /* Exit clone and disconnect from remote. */ + if (!abort_net) + { + auto err2= remote_command(COM_EXIT, SUBCOM_NONE, false); + /* if COM_EXIT is unsuccessful, abort the connection */ + if (err2 != 0) + abort_net= true; + + log_error(get_thd(), true, err2, "Task COM_EXIT"); + } + else + log_error(get_thd(), true, err, "Task skip COM_EXIT"); + + /* If clone is successful, clear any error happened during exit. */ + const bool clear_err= (err == 0); + clone_disconnect(get_thd(), m_conn, abort_net, clear_err); + + snprintf(info_mesg, MESG_SZ, "Task Disconnect : abort: %s", + abort_net ? "true" : "false"); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + + m_conn= nullptr; + + /* Set any error to storage to inform other tasks */ + if (err != 0 && m_storage_active) + hton_clone_apply_error(m_server_thd, m_share->m_storage_vec, m_tasks, + err); + + /* Wait for concurrent tasks to finish. */ + wait_for_workers(); + + if (restart && thd_killed(get_thd())) + { + assert(is_master()); + assert(err != 0); + break; + } + } while (err != 0 && restart && restart_count < CLONE_MAX_RESTART); + + /* Check if storage is initialized and close. */ + if (m_storage_initialized) + { + hton_clone_apply_end(m_server_thd, m_share->m_storage_vec, m_tasks, err); + + m_storage_initialized= false; + m_storage_active= false; + } + + if (m_acquired_backup_lock) + { + assert(is_master()); + assert(get_data_dir() == nullptr); + + /* Don't release the backup lock for success case. Server would be + restarted once the call returns. */ + if (err != 0) + { + // mysql_service_mysql_backup_lock->release(get_thd()); + m_acquired_backup_lock= false; + } + } + /* End PFS table state. */ + const char *err_mesg= nullptr; + uint32_t err_number= 0; + clone_get_error(get_thd(), &err_number, &err_mesg); + pfs_end_state(err_number, err_mesg); + return err; +} + +int Client::connect_remote(bool is_restart, bool use_aux) +{ +#if 0 + MYSQL_SOCKET conn_socket; + mysql_clone_ssl_context ssl_context; + + ssl_context.m_enable_compression= clone_enable_compression; + ssl_context.m_server_extn= + ssl_context.m_enable_compression ? &m_conn_server_extn : nullptr; + ssl_context.m_ssl_mode= m_share->m_ssl_mode; + + /* Get Clone SSL configuration parameter value safely. */ + Key_Values ssl_configs= {{"clone_ssl_key", ""}, {"clone_ssl_cert", ""}, + {"clone_ssl_ca", ""}}; + auto err= clone_get_configs(get_thd(), static_cast(&ssl_configs)); + + if (err != 0) + return err; + + ssl_context.m_ssl_key= nullptr; + ssl_context.m_ssl_cert= nullptr; + ssl_context.m_ssl_ca= nullptr; + + if (ssl_configs[0].second.length() > 0) + ssl_context.m_ssl_key= ssl_configs[0].second.c_str(); + + if (ssl_configs[1].second.length() > 0) + ssl_context.m_ssl_cert= ssl_configs[1].second.c_str(); + + if (ssl_configs[2].second.length() > 0) + ssl_context.m_ssl_ca= ssl_configs[2].second.c_str(); + + const size_t MESG_SZ= 128; + char info_mesg[MESG_SZ]; + /* Establish auxiliary connection */ + if (use_aux) + { + /* Only master creates the auxiliary connection */ + if (!is_master()) + return 0; + + /* Connect to remote server and load clone protocol. */ + m_conn_aux.m_conn= clone_connect(nullptr, m_share->m_host, m_share->m_port, + m_share->m_user, m_share->m_passwd, + &ssl_context, &conn_socket); + + if (m_conn_aux.m_conn == nullptr) + { + /* Disconnect from remote and return */ + err= remote_command(COM_EXIT, SUBCOM_NONE, false); + log_error(get_thd(), true, err, "Source Task COM_EXIT"); + + bool abort_net= (err != 0); + clone_disconnect(get_thd(), m_conn, abort_net, false); + snprintf(info_mesg, MESG_SZ, "Source Task Disconnect: abort: %s", + abort_net ? "true" : "false"); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + + m_conn= nullptr; + return ER_CLONE_DONOR; + } + return 0; + } + + uint loop_count= 0; + auto start_time= Clock::now(); + + while (true) + { + auto connect_time= Clock::now(); + + /* Connect to remote server and load clone protocol. */ + m_conn= clone_connect(m_server_thd, m_share->m_host, m_share->m_port, + m_share->m_user, m_share->m_passwd, &ssl_context, + &conn_socket); + if (m_conn != nullptr) + break; + + if (!is_master() || !is_restart || + s_reconnect_timeout == Time_Sec::zero()) + return ER_CLONE_DONOR; + + ++loop_count; + snprintf(info_mesg, MESG_SZ, "Source re-connect failed: count: %u", + loop_count); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, info_mesg); + + if (is_master() && thd_killed(get_thd())) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + + /* Check and exit if we have exceeded total reconnect time. */ + auto cur_time= Clock::now(); + auto elapsed_time= cur_time - start_time; + + if (elapsed_time > s_reconnect_timeout) + return ER_CLONE_DONOR; + + /* Check and sleep between multiple connect attempt. */ + auto next_connect_time= connect_time + s_reconnect_interval; + + if (next_connect_time > cur_time) + std::this_thread::sleep_until(next_connect_time); + } + + m_ext_link.set_socket(conn_socket); +#endif + return 0; +} + +bool Client::plugin_is_loadable(std::string &so_name) +{ + Key_Values configs= {{"plugin_dir", ""}}; + auto err= clone_get_configs(get_thd(), static_cast(&configs)); + + if (err != 0) + return false; + + std::string path(configs[0].second); + path.append("/"); + path.append(so_name); + + return clone_os_test_load(path); +} + +inline LEX_CSTRING to_lex_cstring(const char *s) +{ + LEX_CSTRING cstr= {s, s != nullptr ? strlen(s) : 0}; + return cstr; +} + +bool Client::plugin_is_installed(std::string &plugin_name) +{ + /* Attempt to lock plugin by name. */ + auto plugin_name_str= to_lex_cstring(plugin_name.c_str()); + auto plugin= my_plugin_lock_by_name( + get_thd(), &plugin_name_str, MYSQL_ANY_PLUGIN); + + if (plugin) + { + plugin_unlock(get_thd(), plugin); + return true; + } + return false; +} + +int Client::validate_remote_params() +{ + int last_error= 0; + + /* Validate plugins from old version CLONE_PROTOCOL_VERSION_V1.*/ + for (auto &plugin_name : m_parameters.m_plugins) + { + assert(m_share->m_protocol_version == CLONE_PROTOCOL_VERSION_V1); + + if (plugin_is_installed(plugin_name)) + continue; + + /* Plugin is not installed. */ + my_error(ER_CLONE_PLUGIN_MATCH, MYF(0), plugin_name.c_str()); + last_error= ER_CLONE_PLUGIN_MATCH; + } + + /* Validate plugins and check if shared objects can be loaded. */ + for (auto &plugin : m_parameters.m_plugins_with_so) + { + assert(m_share->m_protocol_version > CLONE_PROTOCOL_VERSION_V1); + + auto &plugin_name= plugin.first; + auto &so_name= plugin.second; + + if (plugin_is_installed(plugin_name)) + continue; + + /* Built-in plugins with no shared object should already be installed. */ + assert(!so_name.empty()); + + if (so_name.empty() || plugin_is_loadable(so_name)) + continue; + + /* Donor plugin is not there in recipient. */ + my_error(ER_CLONE_PLUGIN_MATCH, MYF(0), plugin_name.c_str()); + last_error= ER_CLONE_PLUGIN_MATCH; + } + + /* Validate character sets */ + auto err= clone_validate_charsets( + get_thd(), static_cast(&m_parameters.m_charsets)); + if (err != 0) + last_error= err; + + /* Validate configurations */ + err= clone_validate_configs( + get_thd(), static_cast(&m_parameters.m_configs)); + if (err != 0) + last_error= err; + + return last_error; +} + +int Client::extract_string(const uchar *&packet, size_t &length, + String_Key &str) +{ + /* Check length. */ + if (length >= 4) + { + auto name_length= uint4korr(packet); + length-= 4; + packet+= 4; + + /* Check length. */ + if (length >= name_length) + { + str.clear(); + if (name_length > 0) + { + auto char_str= reinterpret_cast(packet); + auto str_len= static_cast(name_length); + str.assign(char_str, str_len); + + length-= name_length; + packet+= name_length; + } + return 0; + } + } + const int err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC response length for parameters"); + return err; +} + +int Client::extract_key_value(const uchar *&packet, size_t &length, + Key_Value &keyval) +{ + /* Get configuration parameter name. */ + String_Key key; + auto err= extract_string(packet, length, key); + if (err != 0) + return err; /* purecov: inspected */ + + /* Get configuration parameter value */ + String_Key value; + err= extract_string(packet, length, value); + if (err == 0) + keyval= std::make_pair(key, value); + + return err; +} + +int Client::add_plugin(const uchar *packet, size_t length) +{ + /* Get plugin name. */ + String_Key plugin_name; + auto err= extract_string(packet, length, plugin_name); + + if (err == 0) + m_parameters.m_plugins.push_back(plugin_name); + + return err; +} + +int Client::add_plugin_with_so(const uchar *packet, size_t length) +{ + /* Get plugin name name and shared object name. */ + Key_Value plugin; + + auto err= extract_key_value(packet, length, plugin); + + if (err == 0) + m_parameters.m_plugins_with_so.push_back(plugin); + + return err; +} + +int Client::add_charset(const uchar *packet, size_t length) +{ + /* Get character set collation name. */ + String_Key charset_name; + auto err= extract_string(packet, length, charset_name); + + if (err == 0) + m_parameters.m_charsets.push_back(charset_name); + + return err; +} + +void Client::use_other_configs() +{ + /* Keep default as 5 minutes if remote is old version plugin and has not sent + the configuration */ + s_reconnect_timeout= Time_Min(5); + + for (auto &key_val : m_parameters.m_other_configs) + { + auto &config_name= key_val.first; + auto res= config_name.compare("clone_donor_timeout_after_network_failure"); + if (res == 0) + { + try + { + int timeout_minutes= std::stoi(key_val.second); + s_reconnect_timeout= Time_Min(timeout_minutes); + } + catch (...) + { + assert(false); + } + } + } +} + +int Client::add_config(const uchar *packet, size_t length, bool other) +{ + /* Get configuration parameter name and value. */ + Key_Value config; + + auto err= extract_key_value(packet, length, config); + + if (err != 0) + return err; + + if (other) + m_parameters.m_other_configs.push_back(config); + else + m_parameters.m_configs.push_back(config); + + return 0; +} + +int Client::remote_command(Command_RPC com, Sub_Command sub, bool use_aux) +{ + size_t cmd_buff_len; + + /* Prepare command buffer */ + auto err= prepare_command_buffer(com, sub, cmd_buff_len); + + if (err != 0) + return err; + + assert(cmd_buff_len <= m_cmd_buff.m_length); + + /* Use auxiliary connection for ACK */ + auto conn= use_aux ? m_conn_aux.m_conn : m_conn; + + assert(conn != nullptr); + + auto command= static_cast(com); + /* Send remote command */ + err= clone_send_command(get_thd(), conn, !use_aux, command, + m_cmd_buff.m_buffer, cmd_buff_len); + if (err != 0) + return err; + + /* Receive response from remote server */ + err= receive_response(com, use_aux); + + /* Re-Check and match remote server parameters. Old server 8.0.17-19 + would send configurations later and this is must to recheck it. */ + if (com == COM_INIT && err == 0) + { + err= validate_remote_params(); + + /* Validate local configurations. */ + if (err == 0) + err= validate_local_params(get_thd()); + } + return err; +} + +int Client::init_storage(enum Ha_clone_mode mode, size_t &cmd_len) +{ + /* Get locators for negotiating with remote server */ + auto err= hton_clone_apply_begin(m_server_thd, m_share->m_data_dir, + m_share->m_storage_vec, m_tasks, mode); + if (err == 0) + { + m_storage_initialized= true; + err= serialize_init_cmd(cmd_len); + } + return err; +} + +int Client::prepare_command_buffer(Command_RPC com, Sub_Command sub, + size_t &buf_len) +{ + int err= 0; + buf_len= 0; + + switch (com) + { + case COM_REINIT: + assert(is_master()); + err= init_storage(HA_CLONE_MODE_RESTART, buf_len); + break; + + case COM_INIT: + assert(is_master()); + err= init_storage(HA_CLONE_MODE_VERSION, buf_len); + break; + + case COM_ATTACH: + err= serialize_init_cmd(buf_len); + break; + + case COM_EXECUTE: + err= serialize_exec_cmd(sub, buf_len); + /* No data is passed right now */ + break; + + case COM_ACK: + err= serialize_ack_cmd(buf_len); + break; + + case COM_EXIT: + /* No data is passed right now */ + break; + + case COM_MAX: + [[fallthrough]]; + + default: + assert(false); + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC"); + } + + return err; +} + +int Client::serialize_ack_cmd(size_t &buf_len) +{ + assert(is_master()); + + /* Add Error number */ + buf_len= 4; + + /* Add locator */ + auto loc= &m_share->m_storage_vec[m_conn_aux.m_cur_index]; + buf_len+= loc->serlialized_length(); + + /* Add descriptor */ + buf_len+= 4; + buf_len+= m_conn_aux.m_buf_len; + + /* Allocate for command buffer */ + auto err= m_cmd_buff.allocate(buf_len); + auto buf_ptr= m_cmd_buff.m_buffer; + + if (err != 0) + return err; + + /* Store error number */ + int4store(buf_ptr, m_conn_aux.m_error); + buf_ptr+= 4; + + /* Store Locator */ + buf_ptr+= loc->serialize(buf_ptr); + + /* Store descriptor length */ + int4store(buf_ptr, m_conn_aux.m_buf_len); + buf_ptr+= 4; + + /* Store descriptor length */ + if (m_conn_aux.m_buf_len != 0) + memcpy(buf_ptr, m_conn_aux.m_buffer, m_conn_aux.m_buf_len); + + return 0; +} + +int Client::serialize_init_cmd(size_t &buf_len) +{ + /* Add length of protocol Version */ + buf_len= sizeof(m_share->m_protocol_version); + + /* Add length for DDL timeout value */ + buf_len+= 4; + + /* Add SE and locator length */ + for (auto &loc : m_share->m_storage_vec) + buf_len+= loc.serlialized_length(); + + /* Allocate for command buffer */ + auto err= m_cmd_buff.allocate(buf_len); + auto buf_ptr= m_cmd_buff.m_buffer; + + if (err != 0) + return err; + + /* Store version */ + int4store(buf_ptr, m_share->m_protocol_version); + buf_ptr+= 4; + + /* Store DDL timeout value. Default is no lock. */ + uint32_t timeout_value= NO_BACKUP_LOCK_FLAG; + //clone_ddl_timeout; +#if 0 + if (!clone_block_ddl) + timeout_value|= NO_BACKUP_LOCK_FLAG; +#endif + int4store(buf_ptr, timeout_value); + buf_ptr+= 4; + + /* Store SE information and Locators */ + for (auto &loc : m_share->m_storage_vec) + buf_ptr+= loc.serialize(buf_ptr); + + return err; +} + +int Client::serialize_exec_cmd(Sub_Command sub, size_t &buf_len) +{ + /* Add length for sub-command. */ + buf_len= 1; + + /* Allocate for command buffer. This is a sanity check as the buffer must + have already been allocated in previous RPC. */ + auto err= m_cmd_buff.allocate(buf_len); + auto buf_ptr= m_cmd_buff.m_buffer; + + if (err != 0) + return err; + + /* Store sub_command */ + *buf_ptr= static_cast(sub); + return 0; +} + +int Client::receive_response(Command_RPC com, bool use_aux) +{ + int err= 0; + int saved_err= 0; + bool last_packet= false; + auto &info= get_thread_info(); + + /* Skip setting returned locators for restart */ + const bool skip_apply= (com == COM_REINIT); + + /* For graceful exit we wait for remote to send + the end of command message */ + ulonglong err_start_time= 0; + uint32_t timeout_sec= 0; + + /* Need to wait a little more than DDL lock timeout during INIT + to avoid network timeout. Other than DDL lock, we currently would + need to load the tablespaces [clone_init_tablespaces] and check + through all tables for compression in donor[clone_init_compression]. */ + if (com == COM_INIT) + timeout_sec= 300; + + while (!last_packet) + { + uchar *packet= nullptr; + size_t length= 0, network_length= 0; + + auto conn= use_aux ? m_conn_aux.m_conn : m_conn; + + /* Set current socket as active for clone data connection. */ + err= clone_get_response(get_thd(), conn, !use_aux, timeout_sec, &packet, + &length, &network_length); + if (err != 0) + { + saved_err= err; + break; + } + /* Data length is not updated for meta information. */ + info.update(0, network_length); + + err= handle_response(packet, length, saved_err, skip_apply, last_packet); + + if (handle_error(err, saved_err, err_start_time)) + break; + } + return saved_err; +} + +bool Client::handle_error(int current_err, int &first_err, + ulonglong &first_err_time) +{ + /* If no error, need to continue */ + if (current_err == 0 && first_err == 0) + return false; + + /* If error repeats then exit */ + if (current_err != 0 && first_err != 0) + return true; + + if (current_err != 0) + { + assert(first_err == 0); + first_err= current_err; + first_err_time= microsecond_interval_timer() / 1000; + + /* Set any error to storage to inform other tasks */ + if (m_storage_active) + hton_clone_apply_error(m_server_thd, m_share->m_storage_vec, m_tasks, + current_err); + + /* If network error, no need to wait for remote */ + if (is_network_error(current_err, true)) + return true; + + log_error(get_thd(), true, current_err, + "Wait for remote after local issue"); + return false; + } + + assert(first_err != 0); + + auto cur_time= microsecond_interval_timer() / 1000; + + assert(cur_time >= first_err_time); + assert(current_err == 0); + + /* If wait for remote is long [30 sec] exit */ + if (cur_time - first_err_time > 30 * 1000) + { + log_error(get_thd(), true, first_err, /* purecov: inspected */ + "No error from remote in 30 sec after local issue"); + /* Exit with protocol error */ + first_err= ER_NET_PACKETS_OUT_OF_ORDER; + my_error(first_err, MYF(0)); + + return true; + } + + /* Need to continue till remote reports error or we hit timeout. */ + return false; +} + +int Client::handle_response(const uchar *packet, size_t length, int in_err, + bool skip_loc, bool &is_last) +{ + int err= 0; + + /* Read response command */ + auto res_com= static_cast(packet[0]); + + packet++; + length--; + + is_last= false; + + switch (res_com) + { + case COM_RES_PLUGIN: + err= add_plugin(packet, length); + break; + + case COM_RES_PLUGIN_V2: + err= add_plugin_with_so(packet, length); + break; + + case COM_RES_CONFIG: + err= add_config(packet, length, false); + break; + + case COM_RES_CONFIG_V3: + err= add_config(packet, length, true); + break; + + case COM_RES_COLLATION: + err= add_charset(packet, length); + break; + + case COM_RES_LOCS: + /* Skip applying locator for restart */ + if (!skip_loc && in_err == 0) + err= set_locators(packet, length); + break; + + case COM_RES_LOCKED: + assert(is_master()); + err= set_locked(packet, length); + break; + + case COM_RES_DATA_DESC: + /* Skip processing data in case of an error till last */ + if (in_err == 0) + err= set_descriptor(packet, length); + break; + + case COM_RES_COMPLETE: + is_last= true; + break; + + case COM_RES_ERROR: + err= set_error(packet, length); + is_last= true; + break; + + case COM_RES_DATA: + /* Allow data packet to skip */ + if (in_err != 0) + break; + + /* COM_RES_DATA must follow COM_RES_DATA_DESC and is handled + in apply_file_cbk(). Fall through to return error. */ + [[fallthrough]]; + default: + assert(false); + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC response"); + } + return err; +} + +int Client::set_locked(const uchar *buffer, size_t length) +{ + if (length < 1) + { + int err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC response length for COM_RES_LOCKED"); + return err; + } + + if (!is_master() || static_cast(SUBCOM_MAX) <= *buffer) + { + err_exec_state: + int err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Invalid execution state for COM_RES_LOCKED"); + return err; + } + + auto exec_state= static_cast(*buffer); + if (!m_share->m_state.update_current_state(exec_state)) + goto err_exec_state; + + return 0; +} + +int Client::set_locators(const uchar *buffer, size_t length) +{ + bool init_failed= false; + int err= 0; + + if (length < 4) + { + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC response length for COM_RES_LOCS"); + return err; + } + m_share->m_protocol_version= uint4korr(buffer); + buffer+= 4; + length-= 4; + + assert(m_share->m_protocol_version <= CLONE_PROTOCOL_VERSION); + + Storage_Vector local_locators; + + /* Initialize locators */ + for (auto &st_loc : m_share->m_storage_vec) + { + Locator loc= st_loc; + auto serialized_length= loc.deserialize(get_thd(), buffer); + buffer+= serialized_length; + + if (length < serialized_length || + (loc.m_loc_len == 0 && loc.m_hton->db_type != DB_TYPE_UNKNOWN)) + { + init_failed= true; + break; + } + length-= serialized_length; + local_locators.push_back(loc); + } + + if (length != 0 || init_failed) + { + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC response length for COM_RES_LOCS"); + return err; + } + + auto begin_mode= is_master() ? HA_CLONE_MODE_START : HA_CLONE_MODE_ADD_TASK; + + /* Close the version locators */ + if (is_master()) + { + assert(m_storage_initialized); + assert(!m_storage_active); + + hton_clone_apply_end(m_server_thd, m_share->m_storage_vec, m_tasks, 0); + m_storage_initialized= false; + + /* Check and match remote server parameters. */ + err= validate_remote_params(); + if (err != 0) + return err; + + /* Validate local configurations. */ + err= validate_local_params(get_thd()); + if (err != 0) + return err; + + /* Check and use additional configurations from donor. */ + use_other_configs(); + + /* If cloning to current data directory, prevent any DDL. */ + if (get_data_dir() == nullptr) + { + bool failed= false; // mysql_service_mysql_backup_lock->acquire( + // get_thd(), BACKUP_LOCK_SERVICE_DEFAULT, clone_ddl_timeout); + if (failed) + return ER_LOCK_WAIT_TIMEOUT; + m_acquired_backup_lock= true; + } + } + + /* Move to first stage only after validations are over. */ + pfs_change_stage(0); + + /* Re-initialize SE locators based on remote locators */ + err= hton_clone_apply_begin(m_server_thd, m_share->m_data_dir, + local_locators, m_tasks, begin_mode); + if (err != 0) + { + m_storage_initialized= !m_tasks.empty(); + return err; + } + + /* Master should set locators */ + if (is_master()) + { + int index= 0; + for (auto &st_loc : m_share->m_storage_vec) + st_loc= local_locators[index++]; + } + m_storage_initialized= true; + m_storage_active= true; + return err; +} + +int Client::set_descriptor(const uchar *buffer, size_t length) +{ + int err= 0; + + /* Get Storage Engine */ + auto db_type= static_cast(*buffer); + ++buffer; + length--; + + /* Get Locator Index */ + auto loc_index= *buffer; + ++buffer; + length--; + + auto loc= &m_share->m_storage_vec[loc_index]; + auto hton= loc->m_hton; + + if (hton->db_type != db_type) + { + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Remote descriptor handlerton type mismatch"); + return err; + } + Ha_clone_cbk *clone_callback= new Client_Cbk(this); + + clone_callback->set_data_desc(buffer, static_cast(length)); + clone_callback->clear_flags(); + clone_callback->set_hton(loc->m_hton); + clone_callback->set_loc_index(loc_index); + + /* Apply using descriptor */ + assert(loc_index < m_tasks.size()); + err= hton->clone_interface.clone_apply(get_thd(), loc->m_loc, loc->m_loc_len, + m_tasks[loc_index], 0, clone_callback); + + delete clone_callback; + + if (!is_master() || err == 0 || err == ER_CLONE_DONOR) + return err; + + /* Inform the source database about any local error using the + auxiliary connection. Only master client task should use it. */ + assert(is_master()); + + auto aux_conn= get_aux(); + + aux_conn->reset(); + aux_conn->m_error= err; + aux_conn->m_cur_index= loc_index; + + remote_command(COM_ACK, SUBCOM_NONE, true); + + /* Reset buffers */ + aux_conn->reset(); + return err; +} + +int Client::set_error(const uchar *buffer, size_t length) +{ + auto remote_err= sint4korr(buffer); + + buffer+= 4; + length-= 4; + + const int err= ER_CLONE_DONOR; + + if (is_master()) + { + char err_buf[MYSYS_ERRMSG_SIZE]; + + snprintf(err_buf, MYSYS_ERRMSG_SIZE, "%d : %.*s", + static_cast(remote_err), + static_cast(length), buffer); + + my_error(err, MYF(0), err_buf); + } + return err; +} + +int Client::wait(Time_Sec wait_time) +{ + int ret_error= 0; + auto start_time= Clock::now(); + auto print_time= start_time; + auto sec= wait_time; + auto min= std::chrono::duration_cast(wait_time); + std::ostringstream log_strm; + + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, + "Begin Delay after data drop"); + + sec-= std::chrono::duration_cast(min); + log_strm << "Wait time remaining is " << min.count() << " minutes and " + << sec.count() << " seconds."; + std::string log_str(log_strm.str()); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, log_str.c_str()); + log_strm.str(""); + + for (;;) + { + Time_Msec sleep_time(100); + std::this_thread::sleep_for(sleep_time); + auto cur_time= Clock::now(); + + auto duration_sec= + std::chrono::duration_cast(cur_time - start_time); + + /* Check for total time elapsed. */ + if (duration_sec >= wait_time) + break; + + auto duration_print= + std::chrono::duration_cast(cur_time - print_time); + + if (duration_print.count() >= 1) + { + print_time= Clock::now(); + auto remaining_time= wait_time - duration_sec; + min= std::chrono::duration_cast(remaining_time); + log_strm << "Wait time remaining is " << min.count() << " minutes."; + std::string log_str(log_strm.str()); + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, log_str.c_str()); + log_strm.str(""); + } + + /* Check for interrupt */ + if (thd_killed(get_thd())) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + ret_error= ER_QUERY_INTERRUPTED; + break; + } + } + LogPluginErr(INFORMATION_LEVEL, ER_CLONE_CLIENT_TRACE, + "End Delay after data drop"); + return ret_error; +} + +int Client::delay_if_needed() +{ + /* Delay only if replacing current data directory. */ + if (get_data_dir() != nullptr) + return 0; + return 1; +#if 0 + if (clone_delay_after_data_drop == 0) + return 0; + auto err= wait(Time_Sec(clone_delay_after_data_drop)); + return err; +#endif +} + +int Client_Cbk::file_cbk(Ha_clone_file from_file [[maybe_unused]], + uint len [[maybe_unused]]) +{ + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Remote Clone Client"); + return ER_NOT_SUPPORTED_YET; +} + +int Client_Cbk::buffer_cbk(uchar *from_buffer [[maybe_unused]], uint buf_len) +{ + auto client= get_clone_client(); + + uint64_t data_estimate= 0; + if (is_state_change(data_estimate)) + { + client->pfs_change_stage(data_estimate); + return 0; + } + /* Reset statistics information when state is finished */ + client->update_stat(true); + assert(client->is_master()); + + if (thd_killed(client->get_thd())) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + auto aux_conn= client->get_aux(); + + aux_conn->reset(); + aux_conn->m_buffer= get_data_desc(&buf_len); + + aux_conn->m_buf_len= static_cast(buf_len); + aux_conn->m_cur_index= get_loc_index(); + + /* Send ACK back to remote */ + auto err= client->remote_command(COM_ACK, SUBCOM_NONE, true); + + /* Reset buffers */ + aux_conn->reset(); + return err; +} + +int Client_Cbk::apply_buffer_cbk(uchar *&to_buffer, uint &len) +{ + Ha_clone_file dummy_file; + dummy_file.type= Ha_clone_file::FILE_HANDLE; + dummy_file.file_handle= nullptr; + return apply_cbk(dummy_file, false, to_buffer, len); +} + +int Client_Cbk::apply_file_cbk(Ha_clone_file to_file) +{ + uchar *bufp= nullptr; + uint buf_len= 0; + return apply_cbk(to_file, true, bufp, buf_len); +} + +int Client_Cbk::apply_cbk(Ha_clone_file to_file, bool apply_file, + uchar *&to_buffer, uint &to_len) +{ + auto client= get_clone_client(); + auto &info= client->get_thread_info(); + + MYSQL *conn; + client->get_data_link(conn); + + /* Update statistics information. */ + auto num_workers= client->update_stat(false); + + /* Spawn more concurrent client tasks if suggested. */ + using namespace std::placeholders; + auto func= std::bind(clone_client, _1, _2); + client->spawn_workers(num_workers, func); + + uchar *packet= nullptr; + size_t length= 0, network_length= 0; + + /* Get clone data response command */ + auto err= clone_get_response(client->get_thd(), conn, true, 0, &packet, + &length, &network_length); + if (err != 0) + return err; + + auto res_com= static_cast(packet[0]); + + /* Read response command */ + if (res_com != COM_RES_DATA) + { + assert(false); + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC response, " + "expecting data packet COM_RES_DATA"); + return err; + } + packet++; + length--; + + auto buf_ptr= packet; + + if (!is_os_buffer_cache()) + { + /* Allocate aligned buffer */ + buf_ptr= client->get_aligned_buffer(static_cast(length)); + + if (buf_ptr == nullptr) + { + err= ER_OUTOFMEMORY; + return err; + } + memcpy(buf_ptr, packet, length); + } + + if (apply_file) + err= clone_os_copy_buf_to_file(buf_ptr, to_file, static_cast(length), + get_dest_name()); + else + { + err= 0; + to_buffer= buf_ptr; + to_len= static_cast(length); + } + if (err == 0 && client->is_master() && thd_killed(client->get_thd())) + { + err= ER_QUERY_INTERRUPTED; + my_error(err, MYF(0)); + } + if (err == 0) + { + /* Update data transfer information. */ + info.update(length, network_length); + + /* Check limits and throttle if needed. */ + client->check_and_throttle(); + } + return err; +} +} // namespace myclone diff --git a/plugin/clone/src/clone_hton.cc b/plugin/clone/src/clone_hton.cc new file mode 100644 index 0000000000000..1af196c23b109 --- /dev/null +++ b/plugin/clone/src/clone_hton.cc @@ -0,0 +1,385 @@ +/* Copyright (c) 2018, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_hton.cc +Clone Plugin: Interface with SE handlerton + +*/ + +#include "clone_hton.h" +extern struct handlerton clone_storage_engine; + +/* Namespace for all clone data types */ +namespace myclone { +/** Structure to pass clone information to each storage plugin */ +struct Hton { + /** Clone locator vector */ + Storage_Vector *m_loc_vec; + + /** Clone task vector */ + Task_Vector *m_task_vec; + + /** Current locator index */ + uint m_cur_index; + + /** Error reported during clone */ + int m_err; + + /** Clone type */ + Ha_clone_type m_type; + + /** Clone begin mode */ + Ha_clone_mode m_mode; + + /** clone target data directory */ + const char *m_data_dir; +}; + +size_t Locator::serialize(uchar *buffer) +{ + *buffer= static_cast(m_hton->db_type); + ++buffer; + + int4store(buffer, m_loc_len); + buffer+= 4; + + memcpy(buffer, m_loc, m_loc_len); + + return serlialized_length(); +} + +size_t Locator::deserialize(THD *thd, const uchar *buffer) +{ + auto db_type = static_cast(*buffer); + ++buffer; + + if (!m_hton) + { + /* Should not lock plugin for auxiliary threads */ + assert(thd); + m_hton= (db_type == DB_TYPE_UNKNOWN) ? + &clone_storage_engine : ha_resolve_by_legacy_type(thd, db_type); + } + assert(m_hton->db_type == db_type); + + m_loc_len= uint4korr(buffer); + buffer+= 4; + + m_loc= (m_loc_len == 0) ? nullptr : buffer; + + return serlialized_length(); +} +} // namespace myclone + +static my_bool run_clone_begin(THD *thd, handlerton *hton, + myclone::Hton *clone_arg) +{ + if (!hton->clone_interface.clone_begin) + return FALSE; + + myclone::Locator loc = {hton, nullptr, 0}; + uint32_t task_id = 0; + + assert(clone_arg->m_mode == HA_CLONE_MODE_START); + + clone_arg->m_err= hton->clone_interface.clone_begin( + thd, loc.m_loc, loc.m_loc_len, task_id, clone_arg->m_type, + clone_arg->m_mode); + + clone_arg->m_loc_vec->push_back(loc); + clone_arg->m_task_vec->push_back(task_id); + + return (clone_arg->m_err != 0); +} + +/** Begin clone operation for current storage engine plugin +@param[in,out] thd server thread handle +@param[in] plugin storage plugin +@param[in] arg clone parameters +@return true if failure */ +static my_bool run_hton_clone_begin(THD *thd, plugin_ref plugin, void *arg) +{ + auto clone_arg= static_cast(arg); + auto hton= plugin_data(plugin, handlerton*); + return (hton->db_type == DB_TYPE_INNODB) ? + false : run_clone_begin(thd, hton, clone_arg); +} + +int hton_clone_begin(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, Ha_clone_type clone_type, + Ha_clone_mode clone_mode) { + assert(task_vec.empty()); + /* If Storage locators are empty, construct them here. */ + if (clone_loc_vec.empty()) { + myclone::Hton clone_args; + + clone_args.m_loc_vec = &clone_loc_vec; + clone_args.m_task_vec = &task_vec; + clone_args.m_cur_index = 0; + clone_args.m_err = 0; + clone_args.m_type = clone_type; + clone_args.m_mode = clone_mode; + clone_args.m_data_dir = nullptr; + + /* Make sure to start with Innodb SE. Changing the order doesn't have + functional impact but it could affect concurrency. */ + auto innodb_hton= ha_resolve_by_legacy_type(thd, DB_TYPE_INNODB); + if (innodb_hton) + run_clone_begin(thd, innodb_hton, &clone_args); + + if (!clone_args.m_err) + plugin_foreach(thd, run_hton_clone_begin, MYSQL_STORAGE_ENGINE_PLUGIN, + &clone_args); + + /* Begin Clone SE handlerton. */ + if (!clone_args.m_err) + run_clone_begin(thd, &clone_storage_engine, &clone_args); + + return (clone_args.m_err); + } + + for (auto &loc_iter : clone_loc_vec) { + uint32_t task_id = 0; + +#if !defined(NDEBUG) + Ha_clone_flagset flags; + + loc_iter.m_hton->clone_interface.clone_capability(flags); + + /* TODO: Skip adding task if SE doesn't support */ + if (clone_mode == HA_CLONE_MODE_ADD_TASK) + assert(flags[HA_CLONE_MULTI_TASK]); + + /* TODO: Stop and start if restart not supported */ + if (clone_mode == HA_CLONE_MODE_RESTART) + assert(flags[HA_CLONE_RESTART]); +#endif + auto err = loc_iter.m_hton->clone_interface.clone_begin( + thd, loc_iter.m_loc, loc_iter.m_loc_len, task_id, + clone_type, clone_mode); + + if (err != 0) { + return (err); + } + + task_vec.push_back(task_id); + } + + return (0); +} + +int hton_clone_copy(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, Ha_clone_stage clone_stage, + Ha_clone_cbk *clone_cbk) { + uint index = 0; + + for (auto &loc_iter : clone_loc_vec) { + assert(index < task_vec.size()); + clone_cbk->set_loc_index(index); + clone_cbk->set_hton(loc_iter.m_hton); + + auto err = loc_iter.m_hton->clone_interface.clone_copy( + thd, loc_iter.m_loc, loc_iter.m_loc_len, + task_vec[index], clone_stage, clone_cbk); + + if (err != 0) { + return (err); + } + index++; + } + + return (0); +} + +int hton_clone_end(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, int in_err) { + uint index= 0; + int err= 0; + + for (auto &loc_iter : clone_loc_vec) + { + if (index >= task_vec.size()) + { + /* It is possible that only some of the SEs are initialized + in case of an error. */ + break; + } + auto local_err= loc_iter.m_hton->clone_interface.clone_end( + thd, loc_iter.m_loc, loc_iter.m_loc_len, + task_vec[index], in_err); + + if (local_err != 0) + err= local_err; + ++index; + } + return err; +} + +static my_bool run_clone_apply_begin(THD *thd, handlerton *hton, + myclone::Hton *clone_arg) +{ + if (!hton->clone_interface.clone_apply_begin) + return FALSE; + + myclone::Locator loc = {hton, nullptr, 0}; + uint32_t task_id = 0; + assert(clone_arg->m_mode == HA_CLONE_MODE_VERSION); + + clone_arg->m_err = hton->clone_interface.clone_apply_begin( + thd, loc.m_loc, loc.m_loc_len, task_id, clone_arg->m_mode, + clone_arg->m_data_dir); + + clone_arg->m_loc_vec->push_back(loc); + return (clone_arg->m_err != 0); +} + +/** Begin clone apply for current storage engine plugin +@param[in,out] thd server thread handle +@param[in] plugin storage plugin +@param[in] arg clone parameters +@return true if failure */ +static my_bool run_hton_clone_apply_begin(THD *thd, plugin_ref plugin, void *arg) +{ + auto clone_arg= static_cast(arg); + auto hton= plugin_data(plugin, handlerton*); + return (hton->db_type == DB_TYPE_INNODB) ? + FALSE : run_clone_apply_begin(thd, hton, clone_arg); +} + +int hton_clone_apply_begin(THD *thd, const char *clone_data_dir, + Storage_Vector &clone_loc_vec, Task_Vector &task_vec, + Ha_clone_mode clone_mode) { + /* If Storage locators are empty, construct them here. */ + auto add_task = task_vec.empty(); + + assert(clone_mode == HA_CLONE_MODE_RESTART || task_vec.empty()); + + if (clone_loc_vec.empty()) { + myclone::Hton clone_args; + + clone_args.m_loc_vec = &clone_loc_vec; + clone_args.m_task_vec = &task_vec; + clone_args.m_cur_index = 0; + clone_args.m_err = 0; + clone_args.m_type = HA_CLONE_HYBRID; + clone_args.m_mode = clone_mode; + clone_args.m_data_dir = clone_data_dir; + + auto innodb_hton= ha_resolve_by_legacy_type(thd, DB_TYPE_INNODB); + run_clone_apply_begin(thd, innodb_hton, &clone_args); + + if (!clone_args.m_err) + plugin_foreach(thd, run_hton_clone_apply_begin, + MYSQL_STORAGE_ENGINE_PLUGIN, &clone_args); + /* Begin Clone SE handlerton. */ + if (!clone_args.m_err) + run_clone_apply_begin(thd, &clone_storage_engine, &clone_args); + + return (clone_args.m_err); + } + + uint32_t loop_index [[maybe_unused]]= 0; + + for (auto &loc_iter : clone_loc_vec) + { + uint32_t task_id= 0; + +#if !defined(NDEBUG) + Ha_clone_flagset flags; + loc_iter.m_hton->clone_interface.clone_capability(flags); + + /* TODO: Skip adding task if SE doesn't support */ + if (clone_mode == HA_CLONE_MODE_ADD_TASK) + assert(flags[HA_CLONE_MULTI_TASK]); + + /* TODO: Stop and start if restart no supported */ + if (clone_mode == HA_CLONE_MODE_RESTART) + assert(flags[HA_CLONE_RESTART]); +#endif + auto err= loc_iter.m_hton->clone_interface.clone_apply_begin( + thd, loc_iter.m_loc, loc_iter.m_loc_len, task_id, + clone_mode, clone_data_dir); + + if (err != 0) + return err; + + if (add_task) + task_vec.push_back(task_id); + + assert(task_vec[loop_index] == task_id); + ++loop_index; + } + return 0; +} + +int hton_clone_apply_error(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, int in_err) { + assert(in_err != 0); + + uint index = 0; + for (auto &loc_iter : clone_loc_vec) { + assert(index < task_vec.size()); + auto err = loc_iter.m_hton->clone_interface.clone_apply( + thd, loc_iter.m_loc, loc_iter.m_loc_len, + task_vec[index], in_err, nullptr); + + if (err != 0) { + return (err); + } + ++index; + } + + return (0); +} + +int hton_clone_apply_end(THD *thd, Storage_Vector &clone_loc_vec, + Task_Vector &task_vec, int in_err) { + uint index = 0; + for (auto &loc_iter : clone_loc_vec) + { + /* Task vector could be empty if we are exiting immediately + after initialization */ + uint32_t task_id = 0; + if (!task_vec.empty()) + { + if(index >= task_vec.size()) + { + /* It is possible that only some of the SEs are initialized + in case of an error. */ + break; + } + task_id = task_vec[index]; + } + auto err = loc_iter.m_hton->clone_interface.clone_apply_end( + thd, loc_iter.m_loc, loc_iter.m_loc_len, task_id, + in_err); + + if (err != 0) { + return (err); + } + ++index; + } + + return (0); +} diff --git a/plugin/clone/src/clone_local.cc b/plugin/clone/src/clone_local.cc new file mode 100644 index 0000000000000..9cb2b4ec7a25f --- /dev/null +++ b/plugin/clone/src/clone_local.cc @@ -0,0 +1,399 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_local.cc +Clone Plugin: Local clone implementation + +*/ + +#include "clone_local.h" +#include "clone_os.h" +#include +#include + +// #include "sql/sql_thd_internal_api.h" + +/* Namespace for all clone data types */ +namespace myclone { + +/** Start concurrent clone operation. +@param[in] share shared client information +@param[in] server shared server handle +@param[in] index index of current thread */ +static void clone_local(Client_Share *share, Server *server, uint32_t index) { + /* Create a session statement and set PFS keys */ + auto thd= clone_start_statement(nullptr, clone_local_thd_key, + PSI_NOT_INSTRUMENTED, "clone_local"); + Local clone_inst(thd, server, share, index, false); + + /* Worker task has already reported the error. We ignore any error + returned here. */ + static_cast(clone_inst.clone_exec()); + + /* Drop the statement and session */ + clone_finish_statement(thd); +} + +Local::Local(THD *thd, Server *server, Client_Share *share, uint32_t index, + bool is_master) + : m_clone_server(server), m_clone_client(thd, share, index, is_master) {} + +int Local::clone() { + /* Begin PFS state if no concurrent clone in progress. */ + auto err = m_clone_client.pfs_begin_state(); + if (err != 0) { + return (err); + } + + /* Move to first stage. */ + m_clone_client.pfs_change_stage(0); + + /* Execute clone */ + err = clone_exec(); + + /* End PFS table state. */ + const char *err_mesg = nullptr; + uint32_t err_number = 0; + auto thd = m_clone_client.get_thd(); + + clone_get_error(thd, &err_number, &err_mesg); + m_clone_client.pfs_end_state(err_number, err_mesg); + return (err); +} + +int Local::clone_exec() { + auto thd = m_clone_client.get_thd(); + auto dir_name = m_clone_client.get_data_dir(); + auto is_master = m_clone_client.is_master(); + //auto acquire_backup_lock = (is_master && clone_block_ddl); + //auto num_workers = m_clone_client.get_max_concurrency() - 1; + + auto &client_vector = m_clone_client.get_storage_vector(); + auto &client_tasks = m_clone_client.get_task_vector(); + auto &server_vector = m_clone_server->get_storage_vector(); + + Task_Vector server_tasks; + server_tasks.reserve(MAX_CLONE_STORAGE_ENGINE); + + auto begin_mode = is_master ? HA_CLONE_MODE_START : HA_CLONE_MODE_ADD_TASK; + + /* Begin clone copy from source. */ + auto error = hton_clone_begin(thd, server_vector, server_tasks, + HA_CLONE_HYBRID, begin_mode); + if (error != 0) + { + if (!server_tasks.empty()) + hton_clone_end(thd, server_vector, server_tasks, error); + return (error); + } + + /* Spawn parallel threads for clone */ + if (is_master) { + /* Copy Server locators to Client. */ + client_vector = server_vector; + + /* Begin clone apply to destination. */ + error = hton_clone_apply_begin(thd, dir_name, client_vector, client_tasks, + begin_mode); + if (error != 0) + { + if (!client_tasks.empty()) + hton_clone_apply_end(thd, client_vector, client_tasks, error); + hton_clone_end(thd, server_vector, server_tasks, error); + return error; + } +#if 0 + /* Spawn concurrent client tasks if auto tuning is OFF. */ + if (!clone_autotune_concurrency) { + /* Limit number of workers based on other configurations. */ + auto to_spawn = m_clone_client.limit_workers(num_workers); + using namespace std::placeholders; + auto func = std::bind(clone_local, _1, m_clone_server, _2); + m_clone_client.spawn_workers(to_spawn, func); + } +#endif + + } else { + /* Begin clone apply to destination. For auxiliary threads, + use server storage locator with current copy state. */ + error = hton_clone_apply_begin(thd, dir_name, server_vector, client_tasks, + begin_mode); + if (error != 0) + { + if (!client_tasks.empty()) + hton_clone_apply_end(thd, client_vector, client_tasks, error); + hton_clone_end(thd, server_vector, server_tasks, error); + return (error); + } + } + + auto exec_callback= [&](Sub_Command sub_state) + { + Ha_clone_stage exec_stage= HA_CLONE_STAGE_MAX; + int error= m_clone_server->get_stage_and_lock(sub_state, exec_stage, + is_master); + if (error != 0) + return error; + + if (is_master) + { + auto share= m_clone_client.get_share(); + share->m_state.update_current_state(sub_state); + } + if (sub_state >= SUBCOM_EXEC_BLOCK_DDL) + { + Ha_clone_cbk *clone_callback = new Local_Callback(this); + + auto buffer_size= m_clone_client.limit_buffer(clone_buffer_size); + clone_callback->set_client_buffer_size(buffer_size); + + /* Copy data from source and apply to destination. */ + error= hton_clone_copy(thd, server_vector, server_tasks, exec_stage, + clone_callback); + delete clone_callback; +#ifndef DBUG_OFF + if (sub_state == SUBCOM_EXEC_BLOCK_DDL) + DEBUG_SYNC_C("after_stage_block_ddl"); +#endif /* DBUG_OFF */ + log_error(thd, true, error, sub_command_str(sub_state)); + } + return error; + }; + + error= m_clone_client.execute(exec_callback); + + /* Wait for concurrent tasks to finish */ + m_clone_client.wait_for_workers(); + + /* End clone apply to destination. */ + hton_clone_apply_end(thd, client_vector, client_tasks, error); + + /* End clone copy from source. */ + hton_clone_end(thd, server_vector, server_tasks, error); + + /* Release backup lock, if needed. */ + if (error && is_master) + { + Ha_clone_stage exec_stage= HA_CLONE_STAGE_MAX; + m_clone_server->get_stage_and_lock(SUBCOM_EXEC_END, exec_stage, true); + } + return (error); +} + +int Local_Callback::file_cbk(Ha_clone_file from_file, uint len) { + assert(!m_apply_data); + + /* Set source file to external handle of "Clone Client". */ + auto ext_link = get_client_data_link(); + + ext_link->set_file(from_file, len); + + auto error = apply_data(); + + return (error); +} + +int Local_Callback::buffer_cbk(uchar *from_buffer, uint buf_len) { + int error = 0; + + if (m_apply_data) { + /* Acknowledge data transfer while in apply phase */ + error = apply_ack(); + return (error); + } + + /* Set source buffer to external handle of "Clone Client". */ + auto ext_link = get_client_data_link(); + + ext_link->set_buffer(from_buffer, buf_len); + + error = apply_data(); + + return (error); +} + +int Local_Callback::apply_ack() { + assert(m_apply_data); + + auto client = get_clone_client(); + + uint64_t data_estimate = 0; + /* Check and update PFS table while beginning state. */ + if (is_state_change(data_estimate)) { + client->pfs_change_stage(data_estimate); + return (0); + } + + /* Update and reset statistics information at state end. */ + client->update_stat(true); + + uint loc_len = 0; + + auto hton = get_hton(); + + auto server = get_clone_server(); + + auto thd = server->get_thd(); + auto server_loc = server->get_locator(get_loc_index(), loc_len); + + /* Use master task ID = 0 */ + auto error = hton->clone_interface.clone_ack(thd, server_loc, loc_len, + 0, 0, this); + + return (error); +} + +int Local_Callback::apply_data() { + uint loc_len = 0; + + auto client = get_clone_client(); + auto client_loc = client->get_locator(get_loc_index(), loc_len); + + auto hton = get_hton(); + auto thd = client->get_thd(); + + /* Check and abort, if killed */ + if (thd_killed(thd)) { + if (client->is_master()) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + } + + return (ER_QUERY_INTERRUPTED); + } + + auto &task_vector = client->get_task_vector(); + + assert(get_loc_index() < task_vector.size()); + auto task_id = task_vector[get_loc_index()]; + + /* Call storage engine to apply the data. */ + assert(!m_apply_data); + m_apply_data = true; + + auto error = hton->clone_interface.clone_apply(thd, client_loc, loc_len, + task_id, 0, this); + + m_apply_data = false; + + return (error); +} + +int Local_Callback::apply_buffer_cbk(uchar *&to_buffer, uint &len) { + Ha_clone_file dummy_file; + dummy_file.type = Ha_clone_file::FILE_HANDLE; + dummy_file.file_handle = nullptr; + return (apply_cbk(dummy_file, false, to_buffer, len)); +} + +int Local_Callback::apply_file_cbk(Ha_clone_file to_file) { + uchar *bufp = nullptr; + uint buf_len = 0; + return (apply_cbk(to_file, true, bufp, buf_len)); +} + +int Local_Callback::apply_cbk(Ha_clone_file to_file, bool apply_file, + uchar *&to_buffer, uint &to_len) { + int error; + + assert(m_apply_data); + + auto client = get_clone_client(); + auto server = get_clone_server(); + auto &info = client->get_thread_info(); + + /* Update statistics. */ + auto num_workers = client->update_stat(false); + + /* Spawn new concurrent client tasks, if needed. */ + using namespace std::placeholders; + auto func = std::bind(clone_local, _1, server, _2); + client->spawn_workers(num_workers, func); + + auto ext_link = get_client_data_link(); + + auto dest_type = ext_link->get_type(); + + if (dest_type == CLONE_HANDLE_BUFFER) { + auto from_buf = ext_link->get_buffer(); + + /* Assert alignment to CLONE_OS_ALIGN for O_DIRECT */ + assert(is_os_buffer_cache() || + from_buf->m_buffer == clone_os_align(from_buf->m_buffer)); + + if (apply_file) { + error = clone_os_copy_buf_to_file(from_buf->m_buffer, to_file, + static_cast(from_buf->m_length), + get_dest_name()); + } else { + error = 0; + to_buffer = from_buf->m_buffer; + to_len = static_cast(from_buf->m_length); + } + + info.update(from_buf->m_length, 0); + + } else { + assert(dest_type == CLONE_HANDLE_FILE); + uchar *buf_ptr; + uint buf_len; + + if (is_os_buffer_cache() && is_zero_copy() && + clone_os_supports_zero_copy()) { + buf_ptr = nullptr; + buf_len = 0; + } else { + /* For direct IO use client buffer. */ + buf_len = client->limit_buffer(clone_buffer_size); + buf_ptr = client->get_aligned_buffer(buf_len); + + if (buf_ptr == nullptr) { + return (ER_OUTOFMEMORY); + } + } + + auto from_file = ext_link->get_file(); + + if (apply_file) { + error = clone_os_copy_file_to_file(from_file->m_file_desc, to_file, + from_file->m_length, buf_ptr, buf_len, + get_source_name(), get_dest_name()); + } else { + to_len = from_file->m_length; + to_buffer = client->get_aligned_buffer(to_len); + if (to_buffer == nullptr) { + return (ER_OUTOFMEMORY); /* purecov: inspected */ + } + + error = clone_os_copy_file_to_buf(from_file->m_file_desc, to_buffer, + to_len, get_source_name()); + } + info.update(from_file->m_length, 0); + } + + /* Check limits and throttle if needed. */ + client->check_and_throttle(); + + return (error); +} +} // namespace myclone diff --git a/plugin/clone/src/clone_os.cc b/plugin/clone/src/clone_os.cc new file mode 100644 index 0000000000000..9be646e660ed1 --- /dev/null +++ b/plugin/clone/src/clone_os.cc @@ -0,0 +1,351 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_os.cc +Clone Plugin: OS specific routines for IO and network + +*/ + +#include "clone_os.h" + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#ifdef __linux__ +#include +#endif + +#ifdef _WIN32 + +/** Zero copy optimization */ +static bool s_zero_copy = false; + +/** Check and assert that file is a HANDLE */ +#define CLONE_OS_CHECK_FILE(file) \ + assert(file.type == Ha_clone_file::FILE_HANDLE) + +/** Implement read for Windows +@param[in] file file descriptor +@param[out] buffer read buffer +@param[in] buf_len length of buffer in bytes +@return error code */ +static ssize_t os_read(Ha_clone_file file, uchar *buffer, uint buf_len) { + assert(file.type == Ha_clone_file::FILE_HANDLE); + + auto file_hdl = static_cast(file.file_handle); + + DWORD bytes_read; + auto result = + ReadFile(file_hdl, buffer, (DWORD)buf_len, &bytes_read, nullptr); + + if (!result) { + auto win_error = GetLastError(); + + if (win_error == ERROR_HANDLE_EOF) { + return (0); + } + + my_osmaperr(win_error); + return (-1); + } + + return (bytes_read); +} + +/** Implement write for Windows +@param[in] file file descriptor +@param[in] buffer write buffer +@param[in] buf_len length of buffer in bytes +@return error code */ +static ssize_t os_write(Ha_clone_file file, uchar *buffer, uint buf_len) { + assert(file.type == Ha_clone_file::FILE_HANDLE); + auto file_hdl = static_cast(file.file_handle); + + DWORD bytes_written; + auto result = + WriteFile(file_hdl, buffer, (DWORD)buf_len, &bytes_written, nullptr); + + if (!result) { + auto win_error = GetLastError(); + my_osmaperr(win_error); + + return (-1); + } + + return (bytes_written); +} + +#else + +/** Zero copy optimization */ +static bool s_zero_copy = true; + +/** Check and assert that file is a descriptor */ +#define CLONE_OS_CHECK_FILE(file) assert(file.type == Ha_clone_file::FILE_DESC) + +/** Map to read system call for non-windows platforms. */ +#define os_read(file, buffer, len) read(file.file_desc, buffer, len) + +/** Map to write system call for non-windows platforms. */ +#define os_write(file, buffer, len) write(file.file_desc, buffer, len) + +#endif + +bool clone_os_supports_zero_copy() { return (s_zero_copy); } + +/** Read data from file to buffer. +@param[in] from_file source file descriptor +@param[in] buffer buffer for reading data +@param[in] request_size length of data to read +@param[in] src_name source file name +@param[out] read_size length of data actually read +@return error code */ +static int read_from_file(Ha_clone_file from_file, uchar *buffer, + uint request_size, const char *src_name, + uint &read_size) { + ssize_t ret_size = 0; + + do { + errno = 0; + ret_size = os_read(from_file, buffer, request_size); + + if (errno == EINTR) { + DBUG_PRINT("debug", ("clone read() interrupted")); + } + + } while (ret_size < 0 && errno == EINTR); + + if (ret_size == -1 || ret_size == 0) { + char errbuf[MYSYS_STRERROR_SIZE]; + + const int error = ER_ERROR_ON_READ; + + my_error(error, MYF(0), src_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + return (error); + } + + read_size = static_cast(ret_size); + + return (0); +} + +int clone_os_copy_file_to_buf(Ha_clone_file from_file, uchar *to_buffer, + uint length, const char *src_name) { + CLONE_OS_CHECK_FILE(from_file); + + /* Assert buffer alignment to CLONE_OS_ALIGN[4K] for O_DIRECT */ + assert(to_buffer == clone_os_align(to_buffer)); + + auto len_left = length; + + while (len_left > 0) { + uint ret_length = 0; + auto error = + read_from_file(from_file, to_buffer, len_left, src_name, ret_length); + + if (error != 0) { + DBUG_PRINT("debug", ("Error: clone read failed." + " Length left = %u", + len_left)); + + return (error); + } + + len_left -= ret_length; + to_buffer += ret_length; + } + + return (0); +} + +int clone_os_copy_file_to_file(Ha_clone_file from_file, Ha_clone_file to_file, + uint length, uchar *buffer, uint buff_len, + const char *src_name, const char *dest_name) { + CLONE_OS_CHECK_FILE(from_file); + + CLONE_OS_CHECK_FILE(to_file); + +#ifdef __linux__ + + while (s_zero_copy && (buffer == nullptr) && length > 0) { + auto ret_size = + sendfile(to_file.file_desc, from_file.file_desc, nullptr, length); + + if (ret_size == -1 || ret_size == 0) { + DBUG_PRINT("debug", ("sendfile returned Error (-1) or (0)" + " src file: %s dest file: %s" + " OS Error no: %d mesg = %s" + " Fallback to read/write.", + src_name, dest_name, errno, strerror(errno))); + + s_zero_copy = false; + break; + } + + auto actual_size = static_cast(ret_size); + + assert(length >= actual_size); + length -= actual_size; + } + + if (length == 0) { + return (0); + } +#endif + int error; + uchar buf_stack[2 * CLONE_OS_ALIGN]; + + /* Use stack buffer if no transfer buffer is passed. */ + if (buffer == nullptr || buff_len < (2 * CLONE_OS_ALIGN)) { + buffer = buf_stack; + + /* Align buffer to CLONE_OS_ALIGN for O_DIRECT */ + buffer = clone_os_align(buffer); + buff_len = CLONE_OS_ALIGN; + } + + /* Assert buffer alignment to CLONE_OS_ALIGN for O_DIRECT */ + assert(buffer == clone_os_align(buffer)); + + while (length > 0) { + auto request_size = (length > buff_len) ? buff_len : length; + uint actual_size = 0; + + error = + read_from_file(from_file, buffer, request_size, src_name, actual_size); + + if (error != 0) { + DBUG_PRINT("debug", ("Error: clone read failed." + " Length left = %u", + length)); + + return (error); + } + + assert(length >= actual_size); + length -= actual_size; + + request_size = actual_size; + + error = clone_os_copy_buf_to_file(buffer, to_file, request_size, dest_name); + + if (error != 0) { + return (error); + } + } + + return (0); +} + +int clone_os_copy_buf_to_file(uchar *from_buffer, Ha_clone_file to_file, + uint length, const char *dest_name) { + CLONE_OS_CHECK_FILE(to_file); + + while (length > 0) { + errno = 0; + auto ret_size = os_write(to_file, from_buffer, length); + + if (errno == EINTR) { + DBUG_PRINT("debug", ("clone write() interrupted")); + continue; + } + + if (ret_size == -1) { + char errbuf[MYSYS_STRERROR_SIZE]; + + my_error(ER_ERROR_ON_WRITE, MYF(0), dest_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + DBUG_PRINT("debug", ("Error: clone write failed." + " Length left = %u", + length)); + + return (ER_ERROR_ON_WRITE); + } + + auto actual_size = static_cast(ret_size); + + assert(length >= actual_size); + + length -= actual_size; + from_buffer += actual_size; + } + + return (0); +} + +int clone_os_send_from_buf(uchar *from_buffer [[maybe_unused]], + uint length [[maybe_unused]], + my_socket socket [[maybe_unused]], + const char *src_name [[maybe_unused]]) { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Remote Clone Send"); + return (ER_NOT_SUPPORTED_YET); +} + +int clone_os_send_from_file(Ha_clone_file from_file [[maybe_unused]], + uint length [[maybe_unused]], + my_socket socket [[maybe_unused]], + const char *src_name [[maybe_unused]]) { + CLONE_OS_CHECK_FILE(from_file); + + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Remote Clone Send"); + return (ER_NOT_SUPPORTED_YET); +} + +int clone_os_recv_to_buf(uchar *to_buffer [[maybe_unused]], + uint length [[maybe_unused]], + my_socket socket [[maybe_unused]], + const char *dest_name [[maybe_unused]]) { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Remote Clone Receive"); + return (ER_NOT_SUPPORTED_YET); +} + +int clone_os_recv_to_file(Ha_clone_file to_file [[maybe_unused]], + uint length [[maybe_unused]], + my_socket socket [[maybe_unused]], + const char *dest_name [[maybe_unused]]) { + CLONE_OS_CHECK_FILE(to_file); + + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Remote Clone Receive"); + return (ER_NOT_SUPPORTED_YET); +} + +bool clone_os_test_load(std::string &path) { + char dlpath[FN_REFLEN]; + + unpack_filename(dlpath, path.c_str()); + auto handle = dlopen(dlpath, RTLD_NOW); + + if (handle == nullptr) { + return false; + } + + dlclose(handle); + return true; +} diff --git a/plugin/clone/src/clone_plugin.cc b/plugin/clone/src/clone_plugin.cc new file mode 100644 index 0000000000000..1b041c8e15ad9 --- /dev/null +++ b/plugin/clone/src/clone_plugin.cc @@ -0,0 +1,646 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_plugin.cc +Clone Plugin: Plugin interface + +*/ + +#include + +#include "clone_client.h" +#include "clone_local.h" +#include "clone_server.h" + +#include +#include +#include +#include + +#define CLONE_PLUGIN_VERSION 0x0100 + +/** Clone plugin name */ +const char *clone_plugin_name = "clone"; + +/** Clone system variable: buffer size for data transfer */ +uint clone_buffer_size; + +/** Clone system variable: Maximum IO bandwidth in MiB/sec */ +uint clone_max_io_bandwidth; + +#if 0 +/** Clone system variable: If clone should block concurrent DDL */ +my_bool clone_block_ddl; + +/** Clone system variable: timeout for DDL lock */ +uint clone_ddl_timeout; +/** Clone system variable: If concurrency is automatically tuned */ +my_bool clone_autotune_concurrency; + +/** Clone system variable: Maximum concurrent threads */ +uint clone_max_concurrency; + +/** Clone system variable: Maximum network bandwidth in MiB/sec */ +uint clone_max_network_bandwidth; + +/** Clone system variable: If network compression is enabled */ +my_bool clone_enable_compression; + +/** Clone system variable: valid list of donor addresses. */ +static char *clone_valid_donor_list; + +/** Clone system variable: SSL private key */ +static char *clone_ssl_key; + +/** Clone system variable: SSL Certificate */ +static char *clone_ssl_cert; + +/** Clone system variable: SSL Certificate authority */ +static char *clone_ssl_ca; + +/** Clone system variable: timeout for clone restart after n/w failure */ +uint clone_restart_timeout; + +/** Clone system variable: time delay after removing data */ +uint clone_delay_after_data_drop; +#endif +/** Key for registering clone allocations with performance schema */ +PSI_memory_key clone_mem_key; + +/** Key for registering clone local worker threads */ +PSI_thread_key clone_local_thd_key; + +/** Key for registering clone client worker threads */ +PSI_thread_key clone_client_thd_key; + +/** Clone Local statement */ +PSI_statement_key clone_stmt_local_key; + +/** Clone Remote client statement */ +PSI_statement_key clone_stmt_client_key; + +/** Clone Remote server statement */ +PSI_statement_key clone_stmt_server_key; + +#ifdef HAVE_PSI_INTERFACE +/** Clone memory key for performance schema */ +static PSI_memory_info clone_memory[] = { + + {&clone_mem_key, "data", 0}}; + +/** Clone thread key for performance schema */ +static PSI_thread_info clone_threads[] = { + {&clone_local_thd_key, "clone_local", 0}, + {&clone_client_thd_key, "clone_client", 0}}; + +static PSI_statement_info clone_stmts[] = {{0, "local", 0}, + {0, "client", 0}, + {0, "server", 0}}; +#endif /* HAVE_PSI_INTERFACE */ + +/* Namespace for all clone data types */ +namespace myclone { + +void LogPluginErr(enum loglevel level, int error, const char* string) +{ + myf flags= ME_ERROR_LOG_ONLY; + const char* format= my_get_err_msg(error); + switch (level) + { + case ERROR_LEVEL: + my_printf_error(error, format, flags, string); + break; + case WARNING_LEVEL: + my_printf_error(error, format, flags|ME_WARNING, string); + break; + case INFORMATION_LEVEL: + my_printf_error(error, format, flags|ME_NOTE, string); + break; + } + return; +} + +int validate_local_params(THD *thd) +{ + /* Check if network packet size is enough. */ + Key_Values local_configs = {{"max_allowed_packet", ""}}; + + int err= clone_get_configs(thd, static_cast(&local_configs)); + + if (err != 0) + return (err); + + const std::string &val_str = local_configs[0].second; + + long long val = 0; + bool is_exception = false; + + try + { + val = std::stoll(val_str); + } catch (...) { + is_exception = true; /* purecov: inspected */ + } + + if (is_exception || val <= 0) { + /* purecov: begin deadcode */ + assert(false); + my_error(ER_INTERNAL_ERROR, MYF(0), + "Error extracting integer value for" + "'max_allowed_packet' configuration"); + return (ER_INTERNAL_ERROR); + /* purecov: end */ + } + + if (val < longlong{CLONE_MIN_NET_BLOCK}) + { + err = ER_CLONE_NETWORK_PACKET; + my_error(err, MYF(0), CLONE_MIN_NET_BLOCK, val); + } + return err; +} + +} // namespace myclone + +using Donor_Callback = std::function; + +/** Scan through donor list and call back after extracting host and port. +@param[in] donor_list all donor server list +@param[in] callback callback function +@return true, if scan is successful or match is found. */ +static bool scan_donor_list(const std::string &donor_list, + Donor_Callback callback) +{ + size_t comma_pos = 0; + size_t begin_pos = 0; + + try + { + /* Don't allow space in donor list. */ + auto space_pos = donor_list.find(" "); + if (space_pos != std::string::npos) { + return (false); + } + /* Scan through all entries. */ + while (comma_pos != std::string::npos) + { + comma_pos = donor_list.find(",", begin_pos); + auto entry_len = comma_pos; + + if (entry_len != std::string::npos) + { + if (comma_pos <= begin_pos) + return false; + /* Exclude the comma separator. */ + entry_len = comma_pos - begin_pos; + } + + const std::string entry = donor_list.substr(begin_pos, entry_len); + auto colon_pos = entry.find(":"); + + /* Bad entry if no separator is found or found in beginning. */ + if (colon_pos == std::string::npos || colon_pos == 0) + return false; + + auto port_str = entry.substr(colon_pos + 1); + /* Allow only decimal digit in PORT. */ + for (char &digit : port_str) + { + if (std::isdigit(digit) == 0) + return false; + } + auto valid_port = static_cast(std::stoi(port_str)); + auto valid_host = entry.substr(0, colon_pos); + + bool match = callback(valid_host, valid_port); + + if (match) + return true; + + /* Set next begin position. */ + begin_pos = comma_pos + 1; + } + } + catch (...) + { /* purecov: inspected */ + /* If entry format is bad, return. */ + return false; /* purecov: inspected */ + } + return true; +} + +/** Validate the and (&configs)); + if (err != 0) + return err; + auto &valid_str = configs[0].second; + bool found = false; + + Donor_Callback callback = [&](std::string &valid_host, uint32_t valid_port) + { + /* Host in MySQL is case insensitive and converted to lower case. */ + auto transform_lower = [](unsigned char c) + { + return static_cast(std::tolower(c)); + }; + std::transform(valid_host.begin(), valid_host.end(), valid_host.begin(), + transform_lower); + + /* Check if input matches with configured host and port. */ + if (0 == valid_host.compare(host) && port == valid_port) + found = true; + return found; + }; + + static_cast(scan_donor_list(valid_str, callback)); + + if (found) + return 0; + + char err_buf[MYSYS_ERRMSG_SIZE]; + + snprintf(err_buf, sizeof(err_buf), + "%s:%u is not found in " + "clone_valid_donor_list: %s", + host, port, valid_str.c_str()); + + my_error(ER_CLONE_SYS_CONFIG, MYF(0), err_buf); + + return (ER_CLONE_SYS_CONFIG); +} + +using SYS_VAR = struct st_mysql_sys_var; + +#if 0 +/** Check valid_donor_list format ":,(sizeof(temp_buffer)); + + auto addrs_cstring = value->val_str(value, temp_buffer, &buf_len); + + if (addrs_cstring && (addrs_cstring == temp_buffer)) + addrs_cstring = thd_strmake(thd, addrs_cstring, buf_len); + + if (addrs_cstring == nullptr) + { + /* purecov: begin deadcode */ + (*(const char **)save) = nullptr; + /* NULL is a valid value */ + return 0; + /* purecov: end */ + } + + const std::string addrs(addrs_cstring); + + Donor_Callback callback = [](std::string, uint32_t) { return (false); }; + + bool success = scan_donor_list(addrs_cstring, callback); + + if (!success) + { + (*(const char **)save) = nullptr; + my_error(ER_CLONE_SYS_CONFIG, MYF(0), + "Invalid Format. Please enter " + "\":,...\"' with no extra space"); + return (ER_CLONE_SYS_CONFIG); + } + *(const char **)save = addrs_cstring; + return 0; +} +#endif + +/** Initialize clone plugin +@param[in] plugin_info server plugin handle +@return error code */ +static int plugin_clone_init(MYSQL_PLUGIN plugin_info [[maybe_unused]]) +{ + auto error = clone_handle_create(clone_plugin_name); + + /* During DB creation skip PFS dynamic tables. PFS is not fully initialized + at this point. */ + bool skip_pfs_tables = false; + if (error == ER_SERVER_SHUTDOWN) + skip_pfs_tables = true; + else if (error != 0) + return error; + + if (!skip_pfs_tables && myclone::Table_pfs::acquire_services()) + { + myclone::LogPluginErr(ERROR_LEVEL, ER_CLONE_CLIENT_TRACE, + "PFS table creation failed"); + return -1; + } + +#ifdef HAVE_PSI_INTERFACE + /* Register memory key */ + int count = static_cast(sizeof(clone_memory) / sizeof(clone_memory[0])); + + mysql_memory_register(clone_plugin_name, clone_memory, count); + + /* Register thread keys */ + count = static_cast(sizeof(clone_threads) / sizeof(clone_threads[0])); + + mysql_thread_register(clone_plugin_name, clone_threads, count); + + /* Register statement keys */ + count = static_cast(sizeof(clone_stmts) / sizeof(clone_stmts[0])); + + mysql_statement_register(clone_plugin_name, clone_stmts, count); + + /* Set the statement key values */ + assert(count >= 3); + clone_stmt_local_key = clone_stmts[0].m_key; + clone_stmt_client_key = clone_stmts[1].m_key; + clone_stmt_server_key = clone_stmts[2].m_key; +#endif + + init_clone_storage_engine(); + return (0); +} + +/** Uninitialize clone plugin +@param[in] plugin_info server plugin handle +@return error code */ +static int plugin_clone_deinit(MYSQL_PLUGIN plugin_info [[maybe_unused]]) { + deinit_clone_storage_engine(); + auto error = clone_handle_drop(); + + if (error != ER_SERVER_SHUTDOWN) { + myclone::Table_pfs::release_services(); + } + return 0; +} + +/** Clone database from local server. +@param[in,out] thd server thread handle +@param[in] data_dir cloned data directory +@return error code */ +static int plugin_clone_local(THD *thd, const char *data_dir) +{ + myclone::Client_Share client_share(nullptr, 0, nullptr, nullptr, data_dir, 0); + + myclone::Server server(thd, MYSQL_INVALID_SOCKET); + + /* Update session and statement PFS keys */ + assert(thd != nullptr); + clone_start_statement(thd, PSI_NOT_INSTRUMENTED, clone_stmt_local_key, nullptr); + + myclone::Local clone_inst(thd, &server, &client_share, 0, true); + + auto error = clone_inst.clone(); + + return error; +} + +/** Clone database from remote server. +@param[in,out] thd server thread handle +@param[in] remote_host remote host IP address +@param[in] remote_port remote server port +@param[in] remote_user remote user name +@param[in] remote_passwd remote user's password +@param[in] data_dir cloned data directory +@param[in] ssl_mode ssl mode for remote connection +@return error code */ +static int plugin_clone_remote_client(THD *thd, const char *remote_host, + uint remote_port, const char *remote_user, + const char *remote_passwd, + const char *data_dir, int ssl_mode) +{ + /* Validate that donor address matches with preconfigured value. */ + auto error = match_valid_donor_address(thd, remote_host, remote_port); + if (error != 0) + return error; + + myclone::Client_Share client_share(remote_host, remote_port, remote_user, + remote_passwd, data_dir, ssl_mode); + + /* Update session and statement PFS keys */ + assert(thd != nullptr); + + clone_start_statement(thd, PSI_NOT_INSTRUMENTED, clone_stmt_client_key, nullptr); + + myclone::Client clone_inst(thd, &client_share, 0, true); + + error = clone_inst.clone(); + + return error; +} + +/** Clone database and send to remote clone client. +@param[in,out] thd server thread handle +@param[in] socket network socket to remote client +@return error code */ +static int plugin_clone_remote_server(THD *thd, MYSQL_SOCKET socket) +{ + myclone::Server clone_inst(thd, socket); + + auto err = clone_inst.clone(); + + return err; +} + +/** clone plugin interfaces */ +struct Mysql_clone clone_descriptor = { + MariaDB_CLONE_INTERFACE_VERSION, plugin_clone_local, + plugin_clone_remote_client, plugin_clone_remote_server}; + +/** Size of intermediate buffer for transferring data from source +file to network or destination file. Set to high value for faster +data transfer to/from file system. Especially for direct i/o where +disk driver can do parallel IO for transfer. */ +static MYSQL_SYSVAR_UINT(buffer_size, clone_buffer_size, PLUGIN_VAR_RQCMDARG, + "buffer size used by clone for data transfer", nullptr, + nullptr, CLONE_MIN_BLOCK * 4, /* Default = 4M */ + CLONE_MIN_BLOCK, /* Minimum = 1M */ + CLONE_MIN_BLOCK * 256, /* Maximum = 256M */ + CLONE_MIN_BLOCK); /* Block = 1M */ + +/** Maximum IO bandwidth for clone */ +static MYSQL_SYSVAR_UINT(max_data_bandwidth, clone_max_io_bandwidth, + PLUGIN_VAR_RQCMDARG, + "Maximum File data bandwidth for clone in MiB/sec", + nullptr, nullptr, 0, /* Default = 0 unlimited */ + 0, /* Minimum = 0 unlimited */ + 1024 * 1024, /* Maximum = 1 TiB/sec */ + 1); /* Step = 1 MiB/sec */ + +#if 0 +/** If clone should block concurrent DDL */ +static MYSQL_SYSVAR_BOOL(block_ddl, clone_block_ddl, PLUGIN_VAR_NOCMDARG, + "If clone should block concurrent DDL", nullptr, + nullptr, FALSE); /* Allow concurrent ddl by default */ + +/** Time in seconds to wait for DDL lock. Relevant for donor only when +clone_block_ddl is set to true. */ +static MYSQL_SYSVAR_UINT(ddl_timeout, clone_ddl_timeout, PLUGIN_VAR_RQCMDARG, + "Time in seconds to wait for DDL lock", nullptr, + nullptr, 60 * 5, /* Default = 5 min */ + 0, /* Minimum = 0 no wait */ + 60 * 60 * 24 * 30, /* Maximum = 1 month */ + 1); /* Step = 1 sec */ + +/** If concurrency is automatically tuned */ +static MYSQL_SYSVAR_BOOL(autotune_concurrency, clone_autotune_concurrency, + PLUGIN_VAR_NOCMDARG, + "If concurrency is automatically tuned", nullptr, + nullptr, TRUE); /* Enable auto tuning by default */ + +/** Maximum number of concurrent threads for clone */ +static MYSQL_SYSVAR_UINT(max_concurrency, clone_max_concurrency, + PLUGIN_VAR_RQCMDARG, + "Maximum number of concurrent threads for clone", + nullptr, nullptr, + CLONE_DEF_CON, /* Default = 8 threads */ + 1, /* Minimum = 1 thread */ + 128, /* Maximum = 128 threads */ + 1); /* Step = 1 thread */ + +/** Maximum network bandwidth for clone */ +static MYSQL_SYSVAR_UINT(max_network_bandwidth, clone_max_network_bandwidth, + PLUGIN_VAR_RQCMDARG, + "Maximum network bandwidth for clone in MiB/sec", + nullptr, nullptr, 0, /* Default = 0 unlimited */ + 0, /* Minimum = 0 unlimited */ + 1024 * 1024, /* Maximum = 1 TiB/sec */ + 1); /* Step = 1 MiB/sec */ + +/** If data is compressed in network layer. */ +static MYSQL_SYSVAR_BOOL(enable_compression, clone_enable_compression, + PLUGIN_VAR_NOCMDARG, + "If compression is done at network", nullptr, nullptr, + FALSE); /* Disable compression by default */ + +/** List of valid donor addresses allowed to clone from. */ +static MYSQL_SYSVAR_STR(valid_donor_list, clone_valid_donor_list, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC, + "List of valid donor addresses allowed to clone from" + " HOST1:PORT1,HOST2:PORT2", + check_donor_addr_format, nullptr, nullptr); + +/** SSL path name of the SSL private key file */ +static MYSQL_SYSVAR_STR(ssl_key, clone_ssl_key, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC, + "SSL path name of the SSL private key file", nullptr, + nullptr, nullptr); + +/** SSL path name of the public key certificate file */ +static MYSQL_SYSVAR_STR(ssl_cert, clone_ssl_cert, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC, + "SSL path name of the public key certificate file", + nullptr, nullptr, nullptr); + +/** SSL path name of the Certificate Authority (CA) certificate file */ +static MYSQL_SYSVAR_STR(ssl_ca, clone_ssl_ca, + PLUGIN_VAR_OPCMDARG | PLUGIN_VAR_MEMALLOC, + "SSL path name for Certificate Authority (CA) file", + nullptr, nullptr, nullptr); + +/** Donor allows an on going clone operation to resume after short network +failures. This is the time in minutes up to which the donor allows recipient to +re-connect and resume after a network failure. After timeout, donor drops the +current snapshot and the clone operation can no longer be resumed. */ +static MYSQL_SYSVAR_UINT(donor_timeout_after_network_failure, + clone_restart_timeout, PLUGIN_VAR_RQCMDARG, + "Time in minutes up to which donor allows recipient" + " to re-connect and restart cloning after network" + " failure", + nullptr, nullptr, 5, /* Default = 5 min */ + 0, /* Minimum = 0 min: no wait */ + 30, /* Maximum = 30 min */ + 1); /* Step = 1 min */ + +/* Time in seconds to wait after data drop. +In VxFS file system, it was found that the disk space is released +asynchronously after data files are successfully removed. Since it +is FS specific behavior and we could not find any generic way to wait +till the space is completely released, therefore this configuration +is introduced.*/ +static MYSQL_SYSVAR_UINT(delay_after_data_drop, clone_delay_after_data_drop, + PLUGIN_VAR_RQCMDARG, + "Time in seconds to wait after removing data", + nullptr, nullptr, 0, /* Default = 0 no wait */ + 0, /* Minimum = 0 no wait */ + 60 * 60, /* Maximum = 1 hour */ + 1); /* Step = 1 sec */ +#endif + +/** Clone system variables */ +static SYS_VAR *clone_system_variables[] = { + MYSQL_SYSVAR(buffer_size), + MYSQL_SYSVAR(max_data_bandwidth), +#if 0 + MYSQL_SYSVAR(block_ddl), + MYSQL_SYSVAR(ddl_timeout), + MYSQL_SYSVAR(max_concurrency), + MYSQL_SYSVAR(max_network_bandwidth), + MYSQL_SYSVAR(enable_compression), + MYSQL_SYSVAR(autotune_concurrency), + MYSQL_SYSVAR(valid_donor_list), + MYSQL_SYSVAR(ssl_key), + MYSQL_SYSVAR(ssl_cert), + MYSQL_SYSVAR(ssl_ca), + MYSQL_SYSVAR(donor_timeout_after_network_failure), + MYSQL_SYSVAR(delay_after_data_drop), +#endif + nullptr}; + +/** Declare clone plugin */ +maria_declare_plugin(clone_plugin){ + MariaDB_CLONE_PLUGIN, + + &clone_descriptor, + clone_plugin_name, /* Plugin name */ + + "Debarun Banerjee", + "CLONE PLUGIN", /* Plugin descriptive text */ + PLUGIN_LICENSE_GPL, + + plugin_clone_init, /* Plugin Init */ + plugin_clone_deinit, /* Plugin Deinit */ + + CLONE_PLUGIN_VERSION, /* Plugin Version */ + nullptr, /* status variables */ + clone_system_variables, /* system variables */ + "1.0", /* config options */ + MariaDB_PLUGIN_MATURITY_BETA /* flags */ +} /** Declare clone plugin */ +mysql_declare_plugin_end; diff --git a/plugin/clone/src/clone_se.cc b/plugin/clone/src/clone_se.cc new file mode 100644 index 0000000000000..70ca606aef363 --- /dev/null +++ b/plugin/clone/src/clone_se.cc @@ -0,0 +1,1385 @@ +/* + Copyright (c) 2024, 2024, 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-1301, USA +*/ + +/** +@file clone/src/clone_se.cc +Clone Plugin: Common SE data clone +Part of the implementation is taken from extra/mariabackup/common_engine.cc +*/ + +#include "handler.h" +#include "clone_handler.h" +#include "mysqld_error.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mysqld.h" + +extern "C" PSI_file_key get_key_file_frm(); + +namespace common_engine +{ +class Locator +{ + public: + Locator(const Locator *ref_loc, uint32_t clone_index, bool is_copy); + Locator(const unsigned char *serial, size_t serial_length); + + std::pair get_locator() const; + bool operator==(const Locator& other) const; + uint32_t index() const { return m_index; } + + static constexpr uint32_t S_CUR_VERSION= 1; + static constexpr size_t S_MAX_LENGTH= 12; + + private: + void serialize(); + void deserialize(); + + private: + uint32_t m_version= S_CUR_VERSION; + uint32_t m_clone_id= 0; + uint32_t m_index= 0; + unsigned char m_serial[S_MAX_LENGTH]; +}; + +Locator::Locator(const unsigned char *serial, size_t serial_length) +{ + assert(serial_length == S_MAX_LENGTH); + memset(&m_serial[0], 0, S_MAX_LENGTH); + auto cp_length= std::min(serial_length, S_MAX_LENGTH); + memcpy(&m_serial[0], serial, cp_length); + deserialize(); +} + +void Locator::serialize() +{ + unsigned char *ptr= &m_serial[0]; + int4store(ptr, m_version); + ptr+= 4; + int4store(ptr, m_clone_id); + ptr+= 4; + int4store(ptr, m_index); +} + +void Locator::deserialize() +{ + unsigned char *ptr= &m_serial[0]; + m_version= uint4korr(ptr); + ptr+= 4; + m_clone_id= uint4korr(ptr); + ptr+= 4; + m_index= uint4korr(ptr); +} + +std::pair Locator::get_locator() const +{ + return std::make_pair(&m_serial[0], + static_cast(S_MAX_LENGTH)); +} + +bool Locator::operator==(const Locator& other) const +{ + if (m_clone_id != other.m_clone_id) + return false; + assert(m_version == other.m_version); + assert(m_index == other.m_index); + return (m_version == other.m_version && m_index == other.m_index); +} + +class Descriptor +{ + public: + Descriptor(const unsigned char *serial, size_t serial_length); + Descriptor(const std::string &file_name, uint64_t offset); + + std::pair get_file_info() const; + std::pair get_descriptor() const; + + static constexpr size_t S_MAX_META_LENGTH= 12; + static constexpr size_t S_MAX_LENGTH= S_MAX_META_LENGTH + 2 * FN_REFLEN + 1; + static constexpr uint64_t S_MAX_OFFSET= std::numeric_limits::max(); + static constexpr uint64_t S_OFFSET_NO_DATA= S_MAX_OFFSET - 1; + + private: + uint64_t m_file_offset= 0; + size_t m_file_name_len= 0; + unsigned char m_serial[S_MAX_LENGTH]; +}; + +Descriptor::Descriptor(const unsigned char *serial, size_t serial_length) +{ + assert(serial_length <= S_MAX_LENGTH); + memset(&m_serial[0], 0, S_MAX_LENGTH); + auto cp_length= std::min(serial_length, S_MAX_LENGTH); + memcpy(&m_serial[0], serial, cp_length); + + unsigned char *ptr= &m_serial[0]; + m_file_offset= uint8korr(ptr); + ptr+= 8; + m_file_name_len= uint4korr(ptr); +} + +Descriptor::Descriptor(const std::string &file_name, uint64_t offset) +{ + m_file_offset= offset; + m_file_name_len= file_name.length(); + unsigned char *ptr= &m_serial[0]; + memset(ptr, 0, S_MAX_LENGTH); + + int8store(ptr, offset); + ptr+= 8; + + int4store(ptr, static_cast(m_file_name_len)); + ptr+= 4; + + if (m_file_name_len) + { + auto available_length= S_MAX_LENGTH - S_MAX_META_LENGTH; + auto cp_length= static_cast( + std::min(m_file_name_len, available_length)); + memcpy(ptr, file_name.c_str(), cp_length); + } +} + +std::pair Descriptor::get_file_info() const +{ + auto ptr= reinterpret_cast(&m_serial[0]); + ptr+= S_MAX_META_LENGTH; + return std::make_pair(std::string(ptr, m_file_name_len), m_file_offset); +} + +std::pair Descriptor::get_descriptor() const +{ + auto length= static_cast(m_file_name_len + S_MAX_META_LENGTH); + return std::make_pair(&m_serial[0], length); +} + +static int send_data(Ha_clone_cbk *cbk_ctx, const unsigned char* data, + size_t data_len, uint64_t offset, + const std::string &file_name) +{ + Descriptor data_desc(file_name, offset); + auto [desc, desc_len]= data_desc.get_descriptor(); + cbk_ctx->set_data_desc(desc, desc_len); + cbk_ctx->clear_flags(); + cbk_ctx->set_os_buffer_cache(); + return cbk_ctx->buffer_cbk(const_cast(data), + static_cast(data_len)); +} + +static int send_file(File file_desc, uchar *buf, size_t buf_size, + Ha_clone_cbk *cbk_ctx, const std::string &fname, + const std::string &tname, size_t &copied_size) +{ + assert(file_desc >= 0); + assert(buf_size > 0); + if (file_desc < 0 || !cbk_ctx || !buf || buf_size == 0) + { + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "Common SE: Clone send file invalid data"); + return ER_INTERNAL_ERROR; + } + + int err= 0; + bool send_file_name= true; + copied_size= 0; + + while (size_t bytes_read= my_read(file_desc, buf, buf_size, MY_WME)) + { + if (bytes_read == size_t(-1)) + { + my_printf_error(ER_IO_READ_ERROR, "Error: file %s read for table %s", + ME_ERROR_LOG, fname.c_str(), tname.c_str()); + return ER_IO_READ_ERROR; + } + err= send_data(cbk_ctx, buf, bytes_read, Descriptor::S_MAX_OFFSET, + send_file_name ? fname : ""); + if (err) break; + copied_size+= bytes_read; + send_file_name= false; + } + if (!err && copied_size == 0) + err= send_data(cbk_ctx, buf, 0, Descriptor::S_OFFSET_NO_DATA, fname); + return err; +} + +class Table +{ + public: + Table(std::string &db, std::string &table, std::string &fs_name) : + m_db(std::move(db)), m_table(std::move(table)), + m_fs_name(std::move(fs_name)) {} + virtual ~Table() {} + + void add_file_name(const char *file_name) { m_fnames.push_back(file_name); } + virtual int copy(THD *thd, Ha_clone_cbk *cbk_ctx, bool no_lock, + bool finalize); + + std::string &get_db() { return m_db; } + std::string &get_table() { return m_table; } + std::string &get_version() { return m_version; } + + protected: + std::string m_db; + std::string m_table; + std::string m_fs_name; + std::string m_version; + std::vector m_fnames; +}; + +int Table::copy(THD *thd, Ha_clone_cbk *cbk_ctx, bool no_lock, bool) +{ + static const size_t buf_size = 10 * 1024 * 1024; + std::unique_ptr buf; + std::vector files; + File frm_file= -1; + + int result= 0; + bool locked= false; + + std::string full_tname("`"); + full_tname.append(m_db).append("`.`").append(m_table).append("`"); + + if (!no_lock && clone_backup_lock(thd, m_db.c_str(), m_table.c_str())) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error on executing BACKUP LOCK for table %s", ME_ERROR_LOG, + full_tname.c_str()); + result= ER_INTERNAL_ERROR; + goto exit; + } + else + locked= !no_lock; + + frm_file= mysql_file_open(get_key_file_frm(), (m_fs_name + ".frm").c_str(), + O_RDONLY | O_SHARE, MYF(0)); + + if (frm_file < 0 && !m_fnames.empty() && + !clone_common::ends_with(m_fnames[0].c_str(), ".ARZ") && + !clone_common::ends_with(m_fnames[0].c_str(), ".ARM")) + { + // Don't treat it as error, as the table can be dropped after it + // was added to queue for copying + goto exit; + } + + for (const auto &fname : m_fnames) + { + File file= mysql_file_open(0, fname.c_str(),O_RDONLY | O_SHARE, MYF(0)); + if (file < 0) + { + my_printf_error(ER_CANT_OPEN_FILE, + "Error on file %s open during %s table copy", ME_ERROR_LOG, + fname.c_str(), full_tname.c_str()); + result= ER_CANT_OPEN_FILE; + goto exit; + } + files.push_back(file); + } + + if (locked && clone_backup_unlock(thd)) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error on executing BACKUP UNLOCK for table %s", ME_ERROR_LOG, + full_tname.c_str()); + locked= false; + result= ER_INTERNAL_ERROR; + goto exit; + } + locked= false; + buf.reset(new uchar[buf_size]); + + for (size_t i = 0; i < m_fnames.size(); ++i) + { + size_t copied_size= 0; + MY_STAT stat_info; + if (my_fstat(files[i], &stat_info, MYF(0))) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error: failed to get stat info for file %s of table %s", + ME_ERROR_LOG, m_fnames[i].c_str(), full_tname.c_str()); + goto exit; + } + result= send_file(files[i], buf.get(), buf_size, cbk_ctx, m_fnames[i], + full_tname, copied_size); + if (result) + goto exit; + + mysql_file_close(files[i], MYF(0)); + files[i] = -1; + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Copied file %s for table %s, %zu bytes", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), m_fnames[i].c_str(), + full_tname.c_str(), copied_size); + } +exit: + if (frm_file >= 0) + { + m_version= clone_common::read_table_version_id(frm_file); + mysql_file_close(frm_file, MYF(0)); + } + + if (locked && clone_backup_unlock(thd)) + { + my_printf_error(ER_INTERNAL_ERROR, "Error on BACKUP UNLOCK for table %s", + ME_ERROR_LOG, full_tname.c_str()); + } + + for (auto file : files) + if (file >= 0) mysql_file_close(file, MYF(0)); + return result; +} + +// Append-only tables +class Log_Table : public Table +{ + public: + Log_Table(std::string &db, std::string &table, std::string &fs_name) : + Table(db, table, fs_name) {} + + virtual ~Log_Table() { (void)close(); } + + int copy(THD *thd, Ha_clone_cbk *cbk_ctx, bool no_lock, bool finalize) override; + int close(); + + private: + int open(); + + private: + std::vector m_src; +}; + +int Log_Table::open() +{ + assert(m_src.empty()); + + std::string full_tname("`"); + full_tname.append(m_db).append("`.`").append(m_table).append("`"); + + for (const auto &fname : m_fnames) + { + File file= mysql_file_open(0, fname.c_str(),O_RDONLY | O_SHARE, MYF(0)); + if (file < 0) + { + my_printf_error(ER_CANT_OPEN_FILE, + "Error on file %s open during %s log table copy", ME_ERROR_LOG, + fname.c_str(), full_tname.c_str()); + return ER_CANT_OPEN_FILE; + } + m_src.push_back(file); + + MY_STAT stat_info; + if (my_fstat(file, &stat_info, MYF(0))) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error: failed to get stat info for file %s of log table %s", + ME_ERROR_LOG, fname.c_str(), full_tname.c_str()); + return ER_INTERNAL_ERROR; + } + } + + auto frm_file= mysql_file_open(get_key_file_frm(), (m_fs_name + ".frm").c_str(), + O_RDONLY | O_SHARE, MYF(0)); + if (frm_file < 0 && !m_fnames.empty() && + !clone_common::ends_with(m_fnames[0].c_str(), ".ARZ") && + !clone_common::ends_with(m_fnames[0].c_str(), ".ARM")) + { + my_printf_error(ER_CANT_OPEN_FILE, + "Error: .frm file open for log table %s", ME_ERROR_LOG, + full_tname.c_str()); + return ER_CANT_OPEN_FILE; + } + m_version= clone_common::read_table_version_id(frm_file); + mysql_file_close(frm_file, MYF(0)); + return 0; +} + +int Log_Table::close() +{ + while (!m_src.empty()) + { + auto f= m_src.back(); + m_src.pop_back(); + mysql_file_close(f, MYF(0)); + } + return 0; +} + +int Log_Table::copy(THD *thd, Ha_clone_cbk *cbk_ctx, bool no_lock, bool finalize) +{ + int err= 0; + static const size_t buf_size= 10 * 1024 * 1024; + std::string full_tname("`"); + full_tname.append(m_db).append("`.`").append(m_table).append("`"); + + auto err_exit= [&](int err) + { + close(); + return err; + }; + + if (m_src.empty()) + { + err= open(); + if (err) + return err_exit(err); + } + std::unique_ptr buf(new uchar[buf_size]); + for (size_t i= 0; i < m_src.size(); ++i) + { + // .CSM can be rewritten (see write_meta_file() usage in ha_tina.cc) + if (!finalize && clone_common::ends_with(m_fnames[i].c_str(), ".CSM")) + continue; + size_t copied_size= 0; + + int err= send_file(m_src[i], buf.get(), buf_size, cbk_ctx, m_fnames[i], + full_tname, copied_size); + if (err) + return err_exit(err); + + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Copied file %s for log table %s, %zu bytes", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), m_fnames[i].c_str(), + full_tname.c_str(), copied_size); + } + return 0; +} + +class Job_Repository +{ + public: + using Job= std::function; + void add_one(Job &&job); + void finish(int err, Ha_clone_stage stage); + int consume(THD *thd, uint32_t thread_id, Ha_clone_cbk *cbk, + Ha_clone_stage stage, int err); + Ha_clone_stage last_finished_stage(); + private: + std::mutex m_mutex; + std::condition_variable m_cv; + std::queue m_jobs; + bool m_finished[HA_CLONE_STAGE_MAX]= {false}; + int m_error= 0; +}; + +void Job_Repository::add_one(Job &&job) +{ + std::unique_lock lock(m_mutex); + m_jobs.push(std::forward(job)); + lock.unlock(); + m_cv.notify_one(); +} + +void Job_Repository::finish(int err, Ha_clone_stage stage) +{ + std::unique_lock lock(m_mutex); + if (stage < HA_CLONE_STAGE_MAX) + m_finished[stage]= true; + if (err && !m_error) + m_error= err; + lock.unlock(); + m_cv.notify_all(); +} + +int Job_Repository::consume(THD *thd, uint32_t thread_id, Ha_clone_cbk *cbk, + Ha_clone_stage stage, int err) +{ + std::unique_lock lock(m_mutex); + while (!m_finished[stage] || !m_jobs.empty()) + { + while (!m_jobs.empty()) + { + auto job= std::move(m_jobs.front()); + m_jobs.pop(); + lock.unlock(); + /* Even after an error, we need to keep consuming all jobs added as jobs + could hold table object ownership that needs to be freed. The input + error would ensure we don't actually transfer any data after an error. */ + err= job(thd, cbk, thread_id, err); + lock.lock(); + } + if (m_error && !err) + { + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "Common SE: Clone error in concurrent task"); + err= m_error; + break; + } + else if (err && !m_error) + { + m_error= err; + break; + } + m_cv.wait(lock, [&] + { + return (m_finished[stage] || !m_jobs.empty() || m_error); + }); + } + return err; +} + +Ha_clone_stage Job_Repository::last_finished_stage() +{ + Ha_clone_stage last_stage= HA_CLONE_STAGE_MAX; + std::unique_lock lock(m_mutex); + auto stage= HA_CLONE_STAGE_CONCURRENT; + while (stage < HA_CLONE_STAGE_MAX) + { + if (m_finished[stage] == false) + { + last_stage= stage; + break; + } + stage= static_cast(stage + 1); + } + lock.unlock(); + return last_stage; +} + +using table_key_t= std::string; + +inline table_key_t table_key(const std::string &db, const std::string &table) +{ + return std::string(db).append(".").append(table); +} + +struct Thread_Context +{ + int open(const std::string &path, const std::string &file); + void close(); + + uint32_t m_task_id= 0; + File m_file= -1; + std::string m_cur_file; +}; + +int Thread_Context::open(const std::string &path, const std::string &file) +{ + /* Close previous file if there. */ + close(); + + char fullpath[FN_REFLEN]; + fn_format(fullpath, file.c_str(), path.c_str(), "", MYF(MY_RELATIVE_PATH)); + + size_t dirpath_len= 0; + char dirpath[FN_REFLEN]; + dirname_part(dirpath, fullpath, &dirpath_len); + + /* Make schema directory path and create file, if needed. */ + if (my_mkdir(dirpath, 0777, MYF(0)) >= 0 || my_errno == EEXIST) + { + int open_flags= O_WRONLY | O_BINARY | O_APPEND; + m_file= mysql_file_open(0, fullpath, open_flags, MYF(0)); + if (m_file < 0) + { + open_flags|= O_CREAT; + m_file= mysql_file_open(0, fullpath, open_flags, MYF(0)); + } + } + if (m_file < 0) + { + m_file= -1; + my_error(ER_CANT_OPEN_FILE, MYF(ME_ERROR_LOG), fullpath, my_errno); + return ER_CANT_OPEN_FILE; + } + m_cur_file.assign(file); + return 0; +} + +void Thread_Context::close() +{ + if (m_file < 0) + return; + mysql_file_close(m_file, MYF(0)); + m_file= -1; +} + +class Clone_Handle +{ + public: + Clone_Handle(bool is_copy, const Locator *ref_loc, const char *datadir, + uint32_t index) : m_is_copy(is_copy), m_loc(ref_loc, index, is_copy), + m_data_dir(datadir ? datadir : ".") {} + + void set_error(int err); + int check_error(THD *thd); + + int clone_low(THD *thd, uint32_t task_id, + Ha_clone_stage stage, Ha_clone_cbk *cbk); + + int clone(THD *thd, uint32_t task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk); + int apply(THD *thd, uint32_t task_id, Ha_clone_cbk *cbk); + + size_t attach(); + bool detach(size_t id); + + Locator &get_locator() { return m_loc; } + static constexpr size_t S_MAX_TASKS= 128; + + bool max_task_reached() const + { + assert(m_next_task <= S_MAX_TASKS); + return m_next_task >= S_MAX_TASKS; + } + + private: + int scan(const std::unordered_set &exclude_tables, + bool add_processed, bool no_lock, bool collect_log_and_stats); + + void copy_log_tables(bool finalize); + void copy_stats_tables(); + + int copy_table_job(Table *table, bool no_lock, bool delete_table, + bool finalize, THD *thd, Ha_clone_cbk *cbk, + uint32_t thread_id, int in_error); + int copy_file_job(std::string *file_name, THD *thd, Ha_clone_cbk *cbk, + uint32_t thread_id, int in_error); + + private: + bool m_is_copy= true; + /** Number of threads attached; Protected by Clone_Sys::mutex_ */ + size_t m_num_threads= 0; + size_t m_next_task= 0; + int m_error= 0; + + Locator m_loc; + std::string m_data_dir; + std::array m_thread_ctxs; + + Job_Repository m_jobs; + + std::unordered_map> m_log_tables; + std::unordered_map> m_stats_tables; + std::unordered_set m_processed_tables; +}; + +size_t Clone_Handle::attach() +{ + /* ID is the index into the m_thread_ctxs vector. */ + auto id= m_next_task++; + assert(id < S_MAX_TASKS); + + auto &ctx= m_thread_ctxs[id]; + ctx.m_task_id= static_cast(id); + assert(ctx.m_file == -1); + + m_num_threads++; + assert(m_thread_ctxs.size() >= m_num_threads); + + return id; +} + +bool Clone_Handle::detach(size_t id) +{ + auto &ctx= m_thread_ctxs[id]; + ctx.close(); + assert(m_num_threads > 0); + return (0 == --m_num_threads); +} + +int Clone_Handle::copy_file_job(std::string *file_name, THD *thd, + Ha_clone_cbk *cbk, + uint32_t, int in_error) +{ + int err= in_error; + if (err) + { + delete file_name; + return err; + } + File file= mysql_file_open(0, file_name->c_str(), O_RDONLY | O_SHARE, + MYF(0)); + if (file < 0) + { + my_printf_error(ER_CANT_OPEN_FILE, "Error on opening file: %s", + MYF(ME_ERROR_LOG), file_name->c_str()); + err= ER_CANT_OPEN_FILE; + } + else + { + size_t copied_size= 0; + static const size_t buf_size = 10 * 1024 * 1024; + std::unique_ptr buf= std::make_unique(buf_size); + + err= send_file(file, buf.get(), buf_size, cbk, (*file_name), "", + copied_size); + mysql_file_close(file, MYF(0)); + } + delete file_name; + return err; +} + +int Clone_Handle::copy_table_job(Table *table, bool no_lock, bool delete_table, + bool finalize, THD *thd, + Ha_clone_cbk *cbk, uint32_t, + int in_error) +{ + int err= in_error ? in_error : table->copy(thd, cbk, no_lock, finalize); + + /* TODO: Post Copy Hook for DDL */ + // if (!err && m_table_post_copy_hook) + // m_table_post_copy_hook(table->get_db(), table->get_table(), + // table->get_version()); + if (delete_table) + delete table; + + return err; +} + + +int Clone_Handle::scan(const std::unordered_set &exclude_tables, + bool add_processed, bool no_lock, + bool collect_log_and_stats) +{ + my_printf_error(ER_CLONE_SERVER_TRACE, "Common SE: Start scanning common" + " engine tables, need backup locks: %d, collect log and stat tables: %d", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), no_lock, collect_log_and_stats); + std::unordered_map> found_tables; + + std::set ext_list= + {".MYD", ".MYI", ".MRG", ".ARM", ".ARZ", ".CSM", ".CSV", ".MAD", ".MAI"}; + std::set aria_list= {".MAD", ".MAI"}; + + std::set gen_list= + {".frm", ".isl", ".TRG", ".TRN", ".opt", ".par"}; + if (!collect_log_and_stats) + { + std::set copy_gen= gen_list; + ext_list.merge(copy_gen); + } + + clone_common::foreach_file_in_dir(m_data_dir, + [&](const fsys::path& file_path) + { + std::string extn= file_path.extension().string(); + bool is_aria= (aria_list.find(extn) != aria_list.end()); + bool is_gen= (gen_list.find(extn) != gen_list.end()); + + if (!collect_log_and_stats && is_aria) + return; + + /* TODO: Partial Backup */ + // if (check_if_skip_table(file_path)) + // { + // my_printf_error(ER_CLONE_SERVER_TRACE, "Common SE: Skipping %s.", + // MYF(ME_NOTE | ME_ERROR_LOG_ONLY), file_path); + // return; + // } + const char* fpath= nullptr; +#ifdef _WIN32 + std::wstring wstr= file_path.wstring(); + int size= WideCharToMultiByte(CP_UTF8, 0, &wstr[0], + (int)wstr.size(), nullptr, + 0, nullptr, nullptr); + std::string fil_path(size, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], + (int)wstr.size(), &fil_path[0], + size, nullptr, nullptr); + fpath= fil_path.c_str(); +#else /* _WIN32 */ + fpath= file_path.c_str(); +#endif /* _WIN32 */ + auto db_table_fs= + clone_common::convert_filepath_to_tablename(fpath); + auto tk= table_key(std::get<0>(db_table_fs), std::get<1>(db_table_fs)); + + // log and stats tables are only collected in this function, + // so there is no need to filter out them with exclude_tables. + if (collect_log_and_stats) + { + if (clone_common::is_log_table(std::get<0>(db_table_fs).c_str(), + std::get<1>(db_table_fs).c_str())) + { + auto table_it= m_log_tables.find(tk); + if (table_it == m_log_tables.end()) + { + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Log table found: %s", MYF(ME_NOTE | ME_ERROR_LOG_ONLY), + tk.c_str()); + table_it= m_log_tables.emplace(tk, + std::unique_ptr(new Log_Table(std::get<0>(db_table_fs), + std::get<1>(db_table_fs), std::get<2>(db_table_fs)))).first; + } + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Collect log table file: %s", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), fpath); + table_it->second->add_file_name(fpath); + return; + } + // Aria can handle statistics tables + else if (clone_common::is_stats_table(std::get<0>(db_table_fs).c_str(), + std::get<1>(db_table_fs).c_str()) && !is_aria) + { + auto table_it = m_stats_tables.find(tk); + if (table_it == m_stats_tables.end()) + { + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Stats table found: %s", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), tk.c_str()); + table_it= m_stats_tables.emplace(tk, + std::unique_ptr(new Table(std::get<0>(db_table_fs), + std::get<1>(db_table_fs), std::get<2>(db_table_fs)))).first; + } + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Collect stats table file: %s", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), fpath); + table_it->second->add_file_name(fpath); + return; + } + } + else if(is_gen) + { + auto file_name= std::make_unique(fpath); + using namespace std::placeholders; + m_jobs.add_one(std::bind(&Clone_Handle::copy_file_job, this, + file_name.release(), _1, _2, _3, _4)); + return; + } + else if (clone_common::is_log_table(std::get<0>(db_table_fs).c_str(), + std::get<1>(db_table_fs).c_str()) || + clone_common::is_stats_table(std::get<0>(db_table_fs).c_str(), + std::get<1>(db_table_fs).c_str())) + return; + if (is_aria) + return; + + if (exclude_tables.count(tk)) + { + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Skip table %s as it is in exclude list", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), tk.c_str()); + return; + } + auto table_it= found_tables.find(tk); + if (table_it == found_tables.end()) + { + table_it= found_tables.emplace(tk, + std::unique_ptr
(new Table(std::get<0>(db_table_fs), + std::get<1>(db_table_fs), std::get<2>(db_table_fs)))).first; + } + table_it->second->add_file_name(fpath); + }, ext_list); + + for (auto &table_it : found_tables) + { + using namespace std::placeholders; + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table_it.second.release(), no_lock, + true, false, _1, _2, _3, _4)); + if (add_processed) + m_processed_tables.insert(table_it.first); + } + my_printf_error(ER_CLONE_SERVER_TRACE, + "Common SE: Stop scanning common engine tables", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY)); + return 0; +} + +void Clone_Handle::copy_log_tables(bool finalize) +{ + for (auto &table_it : m_log_tables) + { + // Do not execute BACKUP LOCK for log tables as it's supposed + // that they must be copied on BLOCK_DDL and BLOCK_COMMIT locks. + using namespace std::placeholders; + if (finalize) + /* In final state release the table objects */ + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table_it.second.release(), true, true, true, _1, _2, _3, _4)); + else + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table_it.second.get(), true, false, false, _1, _2, _3, _4)); + } + if (finalize) + m_log_tables.clear(); +} + +void Clone_Handle::copy_stats_tables() +{ + for (auto &table_it : m_stats_tables) + { + // Do not execute BACKUP LOCK for stats tables as it's supposed + // that they must be copied on BLOCK_DDL and BLOCK_COMMIT locks. + // Delete stats table object after copy (see copy_table_job()) + using namespace std::placeholders; + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table_it.second.release(), true, true, false, _1, _2, _3, _4)); + } + m_stats_tables.clear(); +} + +class Clone_Sys +{ + public: + int start(bool is_copy, bool attach, Clone_Handle *&clone_hdl, uint32_t &id, + const Locator *ref_loc= nullptr, const char *data_dir= nullptr); + int stop(bool is_copy, Clone_Handle *&clone_hdl, uint32_t task_id); + + Clone_Handle *find(const Locator *in_loc, bool is_copy); + Clone_Handle *get(uint32_t index, bool is_copy); + + uint32_t next_id() { return m_next_clone_id++; } + + static constexpr uint32_t S_MAX_CLONE= 1; + static std::mutex mutex_; + private: + std::mutex m_mutex; + uint32_t m_next_clone_id= 1; + + std::array m_copy_clones; + std::array m_apply_clones; +}; +inline std::mutex Clone_Sys::mutex_; + +int Clone_Sys::start(bool is_copy, bool attach, Clone_Handle *&clone_hdl, + uint32_t &id, const Locator *ref_loc, + const char *data_dir) +{ + if (!attach) + { + /* Create a new clone handle. */ + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + + uint32_t index= 0; + for (auto clone_ : clones) + { + if (clone_ == nullptr) + break; + ++index; + } + if (index >= S_MAX_CLONE) + { + /* Too many active clones .*/ + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(ME_ERROR_LOG), + S_MAX_CLONE); + return ER_CLONE_TOO_MANY_CONCURRENT_CLONES; + } + clones[index]= new(std::nothrow) Clone_Handle(is_copy, ref_loc, data_dir, + index); + clone_hdl= clones[index]; + } + if (!clone_hdl) + { + assert(attach); + /* Operation has finished already */ + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "Common SE: Clone add task refers non-existing clone"); + /* No active clone to attach to. */ + return ER_INTERNAL_ERROR; + } + + if (clone_hdl->max_task_reached()) + { + assert(attach); + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "Common SE: Maximum Tasks reached"); + return ER_INTERNAL_ERROR; + } + id= static_cast(clone_hdl->attach()); + return 0; +} + +int Clone_Sys::stop(bool is_copy, Clone_Handle *&clone_hdl, uint32_t task_id) +{ + bool last= clone_hdl->detach(static_cast(task_id)); + if (last) + { + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + auto index= clone_hdl->get_locator().index(); + assert(clones[index] == clone_hdl); + clones[index]= nullptr; + delete clone_hdl; + clone_hdl= nullptr; + } + return 0; +} + +Clone_Handle *Clone_Sys::find(const Locator *in_loc, bool is_copy) +{ + if (!in_loc) + return nullptr; + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + + for (auto clone_hdl : clones) + { + if (!clone_hdl) + continue; + + auto& loc= clone_hdl->get_locator(); + if (loc == *in_loc) + return clone_hdl; + } + return nullptr; +} + +Clone_Handle *Clone_Sys::get(uint32_t index, bool is_copy) +{ + if (index > S_MAX_CLONE) + return nullptr; + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + return clones[index]; +} + +static Clone_Sys *clone_sys; + +Locator::Locator(const Locator *ref_loc, uint32_t clone_index, bool is_copy) +{ + m_version= S_CUR_VERSION; + if (ref_loc && m_version > ref_loc->m_version) + m_version= ref_loc->m_version; + m_index= clone_index; + + uint32_t ref_id= ref_loc ? ref_loc->m_clone_id : 0; + m_clone_id= is_copy ? clone_sys->next_id() : ref_id; + serialize(); +} + +int Clone_Handle::check_error(THD *thd) +{ + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(ME_ERROR_LOG)); + set_error(ER_QUERY_INTERRUPTED); + } + const std::lock_guard lock(Clone_Sys::mutex_); + return m_error; +} + +void Clone_Handle::set_error(int err) +{ + if (err == 0) + return; + std::unique_lock lock(Clone_Sys::mutex_); + if (m_error) + return; + m_error= err; + lock.unlock(); + + if (m_is_copy) + m_jobs.finish(err, HA_CLONE_STAGE_MAX); +} + +int Clone_Handle::apply(THD *thd, uint32_t task_id, Ha_clone_cbk *cbk) +{ + uint32_t desc_len= 0; + auto desc_buf= cbk->get_data_desc(&desc_len); + + Descriptor clone_desc(desc_buf, desc_len); + auto &ctx= m_thread_ctxs[task_id]; + + auto [file_name, offset]= clone_desc.get_file_info(); + /* Currently the write is append only. */ + assert(offset == Descriptor::S_MAX_OFFSET || + offset == Descriptor::S_OFFSET_NO_DATA); + + int err= 0; + if (!file_name.empty() && (err= ctx.open(m_data_dir, file_name))) + return err; + + if (offset == Descriptor::S_OFFSET_NO_DATA) + { + ctx.close(); + return 0; + } + Ha_clone_file file; + assert(ctx.m_file >= 0); +#ifdef _WIN32 + file.type= Ha_clone_file::FILE_HANDLE; + file.file_handle= static_cast(my_get_osfhandle(ctx.m_file)); +#else + file.type= Ha_clone_file::FILE_DESC; + file.file_desc= ctx.m_file; +#endif /* _WIN32 */ + + cbk->set_os_buffer_cache(); + return cbk->apply_file_cbk(file); +} + +int Clone_Handle::clone_low(THD *thd, uint32_t task_id, + Ha_clone_stage stage, Ha_clone_cbk *cbk) +{ + int err= 0; + std::unordered_set tables_in_use; + + switch (stage) + { + case HA_CLONE_STAGE_CONCURRENT: + break; + case HA_CLONE_STAGE_NT_DML_BLOCKED: + if (task_id != 0) + break; + /* TODO: get_tables_in_use() : "SHOW OPEN TABLES WHERE In_use = 1" */ + err= scan(tables_in_use, true, false, true); + break; + case HA_CLONE_STAGE_DDL_BLOCKED: + if (task_id != 0) + break; + err= scan(m_processed_tables, false, true, false); + if (!err) + copy_log_tables(false); + break; + case HA_CLONE_STAGE_SNAPSHOT: + if (task_id != 0) + break; + copy_log_tables(true); + copy_stats_tables(); + break; + case HA_CLONE_STAGE_END: + break; + case HA_CLONE_STAGE_MAX: + assert(false); + err= ER_INTERNAL_ERROR; + my_error(err, MYF(ME_ERROR_LOG), "Common SE: Invalid Execution Stage"); + break; + } + if (task_id == 0) + m_jobs.finish(err, stage); + err= m_jobs.consume(thd, task_id, cbk, stage, err); + set_error(err); + return err; +} + +int Clone_Handle::clone(THD *thd, uint32_t task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk) +{ + int err= 0; + Ha_clone_stage cur_stage= m_jobs.last_finished_stage(); + while (!err && cur_stage <= stage) + { + err= clone_low(thd, task_id, cur_stage, cbk); + cur_stage= static_cast(cur_stage + 1); + } + return err; +} +} // namespace common_engine + +/** Dummy SE handlerton for cloning common data and SEs that don't have clone +interfaces defined. */ +struct handlerton clone_storage_engine; + +static void clone_get_capability(Ha_clone_flagset &flags) +{ + flags.reset(); + flags.set(HA_CLONE_BLOCKING); + flags.set(HA_CLONE_MULTI_TASK); +} + +static int clone_begin(THD *, const uchar *&loc, uint &loc_len, + uint &task_id, Ha_clone_type, Ha_clone_mode mode) +{ + common_engine::Locator *in_loc= nullptr; + if (loc) + in_loc= new(std::nothrow) common_engine::Locator(loc, loc_len); + int err= 0; + + const std::lock_guard lock(common_engine::Clone_Sys::mutex_); + auto clone_hdl= common_engine::clone_sys->find(in_loc, true); + + switch (mode) + { + case HA_CLONE_MODE_START: + err= common_engine::clone_sys->start(true, false, clone_hdl, task_id, + in_loc); + break; + case HA_CLONE_MODE_ADD_TASK: + err= common_engine::clone_sys->start(true, true, clone_hdl, task_id, + in_loc); + break; + case HA_CLONE_MODE_RESTART: + err=ER_NOT_SUPPORTED_YET; + my_error(ER_NOT_SUPPORTED_YET, MYF(ME_ERROR_LOG), + "Common SE: Clone Restart after network failure"); + break; + case HA_CLONE_MODE_VERSION: + case HA_CLONE_MODE_MAX: + err= ER_INTERNAL_ERROR; + my_error(err, MYF(ME_ERROR_LOG), "Common SE: Clone Begin Invalid Mode"); + assert(false); + } + if (!err && clone_hdl) + { + auto &locator= clone_hdl->get_locator(); + std::tie(loc, loc_len)= locator.get_locator(); + } + delete in_loc; + return err; +} + +static int clone_copy(THD *thd, const uchar *loc, uint loc_len, uint task_id, + Ha_clone_stage stage, Ha_clone_cbk *cbk) +{ + assert(loc); + std::unique_ptr + in_loc(new(std::nothrow) common_engine::Locator(loc, loc_len)); + + auto clone_hdl= common_engine::clone_sys->get(in_loc->index(), true); + int err= clone_hdl ? clone_hdl->check_error(thd) : 0; + + if (!clone_hdl || err != 0) + return err; + + return clone_hdl->clone(thd, task_id, stage, cbk); +} + +static int clone_ack(THD *, const uchar *loc, uint loc_len, + uint, int in_err, Ha_clone_cbk *) +{ + DBUG_ASSERT(loc); + std::unique_ptr + in_loc(new(std::nothrow) common_engine::Locator(loc, loc_len)); + auto clone_hdl= common_engine::clone_sys->get(in_loc->index(), true); + DBUG_ASSERT(clone_hdl); + if (!clone_hdl) + return 0; + clone_hdl->set_error(in_err); + return 0; +} + +static int clone_end(THD *, const uchar *loc, uint loc_len, uint task_id, + int in_err) +{ + assert(loc); + std::unique_ptr + in_loc(new(std::nothrow) common_engine::Locator(loc, loc_len)); + auto clone_hdl= common_engine::clone_sys->get(in_loc->index(), true); + + assert(clone_hdl); + clone_hdl->set_error(in_err); + + const std::lock_guard lock(common_engine::Clone_Sys::mutex_); + return common_engine::clone_sys->stop(true, clone_hdl, task_id); +} + +static int clone_apply_begin(THD *, const uchar *&loc, + uint &loc_len, uint &task_id, Ha_clone_mode mode, + const char *data_dir) +{ + common_engine::Locator *in_loc= nullptr; + if (loc) + in_loc= new(std::nothrow) common_engine::Locator(loc, loc_len); + int err= 0; + + const std::lock_guard lock(common_engine::Clone_Sys::mutex_); + auto clone_hdl= common_engine::clone_sys->find(in_loc, false); + + switch (mode) + { + case HA_CLONE_MODE_VERSION: + case HA_CLONE_MODE_START: + assert(!clone_hdl); + err= common_engine::clone_sys->start(false, false, clone_hdl, task_id, + in_loc, data_dir); + task_id= 0; + break; + case HA_CLONE_MODE_ADD_TASK: + err= common_engine::clone_sys->start(false, true, clone_hdl, task_id, + in_loc); + break; + case HA_CLONE_MODE_RESTART: + err=ER_NOT_SUPPORTED_YET; + my_error(ER_NOT_SUPPORTED_YET, MYF(ME_ERROR_LOG), + "Common SE: Clone Restart after network failure"); + break; + case HA_CLONE_MODE_MAX: + err= ER_INTERNAL_ERROR; + my_error(err, MYF(ME_ERROR_LOG), "Common SE: Clone Begin Invalid Mode"); + assert(false); + } + + /* While attaching tasks, don't overwrite the source locator. */ + if (!err && clone_hdl && mode != HA_CLONE_MODE_ADD_TASK) + { + auto &locator= clone_hdl->get_locator(); + std::tie(loc, loc_len)= locator.get_locator(); + } + delete in_loc; + return err; +} + +static int clone_apply(THD *thd, const uchar *loc, + uint loc_len, uint task_id, int in_err, + Ha_clone_cbk *cbk) +{ + assert(loc); + std::unique_ptr + in_loc(new(std::nothrow) common_engine::Locator(loc, loc_len)); + + auto clone_hdl= common_engine::clone_sys->get(in_loc->index(), false); + + assert(in_err != 0 || cbk != nullptr); + if (clone_hdl && (in_err != 0 || cbk == nullptr)) + { + clone_hdl->set_error(in_err); + my_printf_error(ER_CLONE_CLIENT_TRACE, "Common SE: Set Error Code %d", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), in_err); + return 0; + } + + int err= clone_hdl ? clone_hdl->check_error(thd) : 0; + if (!clone_hdl || err != 0) + return err; + + err= clone_hdl->apply(thd, task_id, cbk); + clone_hdl->set_error(err); + return err; +} + +static int clone_apply_end(THD *, const uchar *loc, uint loc_len, + uint task_id, int in_err) +{ + assert(loc); + std::unique_ptr + in_loc(new(std::nothrow) common_engine::Locator(loc, loc_len)); + auto clone_hdl= common_engine::clone_sys->get(in_loc->index(), false); + assert(clone_hdl); + clone_hdl->set_error(in_err); + + const std::lock_guard lock(common_engine::Clone_Sys::mutex_); + return common_engine::clone_sys->stop(false, clone_hdl, task_id); +} + +void init_clone_storage_engine() +{ + clone_storage_engine.db_type= DB_TYPE_UNKNOWN; + + auto &interface= clone_storage_engine.clone_interface; + interface.clone_capability= clone_get_capability; + + interface.clone_begin= clone_begin; + interface.clone_copy= clone_copy; + interface.clone_ack= clone_ack; + interface.clone_end= clone_end; + + interface.clone_apply_begin= clone_apply_begin; + interface.clone_apply= clone_apply; + interface.clone_apply_end= clone_apply_end; + common_engine::clone_sys= new(std::nothrow) common_engine::Clone_Sys(); +} + +void deinit_clone_storage_engine() +{ + delete common_engine::clone_sys; + common_engine::clone_sys= nullptr; +} diff --git a/plugin/clone/src/clone_server.cc b/plugin/clone/src/clone_server.cc new file mode 100644 index 0000000000000..78506f5908f6f --- /dev/null +++ b/plugin/clone/src/clone_server.cc @@ -0,0 +1,914 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_server.cc +Clone Plugin: Server implementation + +*/ + +#include "clone_server.h" +#include "clone_status.h" +#include "log.h" + +#include "my_byteorder.h" + +/* Namespace for all clone data types */ +namespace myclone { + +Server::Server(THD *thd, MYSQL_SOCKET socket) + : m_server_thd(thd), + m_is_master(false), + m_storage_initialized(false), + m_pfs_initialized(false), + m_acquired_backup_lock(false), + m_protocol_version(CLONE_PROTOCOL_VERSION), + m_client_ddl_timeout(), + m_backup_lock(true) { + m_ext_link.set_socket(socket); + m_storage_vec.reserve(MAX_CLONE_STORAGE_ENGINE); + + m_tasks.reserve(MAX_CLONE_STORAGE_ENGINE); + + m_copy_buff.init(); + m_res_buff.init(); +} + +Server::~Server() { + assert(!m_storage_initialized); + m_copy_buff.free(); + m_res_buff.free(); +} + +int Server::clone() { + int err = 0; + + while (true) { + uchar command= static_cast(COM_RES_ERROR); + uchar *com_buf= nullptr; + size_t com_len= 0; + + err = clone_get_command(get_thd(), &command, &com_buf, &com_len); + + bool done = true; + + if (err == 0) { + err = parse_command_buffer(command, com_buf, com_len, done); + } + + if (err == 0 && thd_killed(get_thd())) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err = ER_QUERY_INTERRUPTED; + } + + /* Send status to client */ + err = send_status(err); + + if (done || err != 0) { + if (m_storage_initialized) { + assert(err != 0); + /* Don't abort clone if worker thread fails during attach. */ + const int in_err = (command == COM_ATTACH) ? 0 : err; + + hton_clone_end(get_thd(), get_storage_vector(), m_tasks, in_err); + m_storage_initialized = false; + } + /* Release if we have acquired backup lock */ + if (m_acquired_backup_lock) { + assert(m_is_master); + Ha_clone_stage exec_stage= HA_CLONE_STAGE_MAX; + get_stage_and_lock(SUBCOM_EXEC_END, exec_stage, m_is_master); + } + break; + } + } + + log_error(get_thd(), false, err, "Exiting clone protocol"); + return (err); +} + +int Server::send_status(int err) { + uchar res_cmd; + char info_mesg[128]; + + if (err == 0) { + /* Send complete response */ + res_cmd = static_cast(COM_RES_COMPLETE); + + err = clone_send_response(get_thd(), false, &res_cmd, sizeof(res_cmd)); + log_error(get_thd(), false, err, "COM_RES_COMPLETE"); + + } else { + /* Send Error Response */ + res_cmd = static_cast(COM_RES_ERROR); + + snprintf(info_mesg, 128, "Before sending COM_RES_ERROR: %s", + is_network_error(err) ? "network " : " "); + log_error(get_thd(), false, err, &info_mesg[0]); + + err = clone_send_error(get_thd(), res_cmd, is_network_error(err)); + log_error(get_thd(), false, err, "After sending COM_RES_ERROR"); + } + + return (err); +} + +int Server::init_storage(Ha_clone_mode mode, uchar *com_buf, size_t com_len) { + auto thd = get_thd(); + + assert(thd != nullptr); + assert(!m_pfs_initialized); + + auto err = deserialize_init_buffer(com_buf, com_len); + + if (err != 0) { + return (err); + } + + if (m_is_master) { + /* Set statement type for master thread */ + clone_start_statement(thd, PSI_NOT_INSTRUMENTED, clone_stmt_server_key, nullptr); + + /* Acquire backup lock */ + if (block_ddl()) { + bool failed = false; // mysql_service_mysql_backup_lock->acquire( + // thd, BACKUP_LOCK_SERVICE_DEFAULT, m_client_ddl_timeout); + + if (failed) { + return (ER_LOCK_WAIT_TIMEOUT); + } + m_acquired_backup_lock = true; + log_error(get_thd(), false, 0, "Acquired backup lock"); + } + } + m_pfs_initialized = true; + + /* Work around to use client DDL timeout while waiting for backup + lock in clone_init_tablespaces if required. */ + /* Get server locators */ + err = hton_clone_begin(get_thd(), get_storage_vector(), m_tasks, + HA_CLONE_HYBRID, mode); + if (err != 0) { + m_storage_initialized= !m_tasks.empty(); + return (err); + } + m_storage_initialized = true; + + if (m_is_master && mode == HA_CLONE_MODE_START) { + /* Validate local configurations. */ + err = validate_local_params(get_thd()); + + if (err == 0) { + /* Send current server parameters for validation. */ + err = send_params(); + } + + if (err != 0) { + return (err); + } + } + /* Send locators back to client */ + err = send_locators(); + return (err); +} + +int Server::send_replication_state() +{ + /* 1. Get binary log position: Following SQLCOM_SHOW_BINLOG_STAT */ + if (!mysql_bin_log.is_open()) + return 0; + + LOG_INFO log_info; + mysql_bin_log.get_current_log(&log_info); + + /* 2. Get last executed GTID: Read gtid_current_pos */ + Key_Values gtid_configs= {{"gtid_current_pos", ""}}; + auto err= clone_get_configs(get_thd(), static_cast(>id_configs)); + + /* 3. TODO: Serialize and send binary log information. */ + return err; +} + +int Server::get_stage_and_lock(Sub_Command sub_cmd, Ha_clone_stage &stage, + bool lock) +{ + int err= 0; + THD *thd= get_thd(); + const char *err_msg= nullptr; + switch (sub_cmd) + { + case SUBCOM_EXEC_CONCURRENT: + if (lock) + { + log_error(thd, false, 0, "Acquiring locks for BACKUP STAGE " + "START"); + err= clone_set_backup_stage(thd, START); + if (err) + { + err_msg= "Failed to acquire locks for BACKUP STAGE START"; + goto err_exit; + } + log_error(thd, false, 0, "Acquired locks for BACKUP STAGE " + "START"); + DEBUG_SYNC_C("backup_stage_start"); + m_acquired_backup_lock= true; + } + stage= HA_CLONE_STAGE_CONCURRENT; + break; + case SUBCOM_EXEC_BLOCK_NT_DML: + if (lock) + { + assert(m_acquired_backup_lock); + log_error(thd, false, 0, "Acquiring locks for BACKUP STAGE " + "FLUSH"); + err= clone_set_backup_stage(thd, FLUSH); + if (err) + { + err_msg= "Failed to acquire locks for BACKUP STAGE FLUSH"; + goto err_exit; + } + log_error(thd, false, 0, "Acquired locks for BACKUP STAGE " + "FLUSH"); + } + stage= HA_CLONE_STAGE_NT_DML_BLOCKED; + break; + case SUBCOM_EXEC_BLOCK_DDL: + if (lock) + { + assert(m_acquired_backup_lock); + log_error(thd, false, 0, "Acquiring locks for BACKUP STAGE " + "BLOCK_DDL"); + err= clone_set_backup_stage(thd, BLOCK_DDL); + if (err) + { + err_msg= "Failed to acquire locks for BACKUP STAGE BLOCK_DDL"; + goto err_exit; + } + log_error(thd, false, 0, "Acquired locks for BACKUP STAGE " + "BLOCK_DDL"); + } + stage= HA_CLONE_STAGE_DDL_BLOCKED; + break; + case SUBCOM_EXEC_SNAPSHOT: + assert(lock); + assert(m_acquired_backup_lock); + log_error(thd, false, 0, "Acquiring locks for BACKUP STAGE " + "BLOCK_COMMIT"); + err= clone_set_backup_stage(thd, BLOCK_COMMIT); + if (err) + { + err_msg= "Failed to acquire locks for BACKUP STAGE BLOCK_COMMIT"; + goto err_exit; + } + log_error(thd, false, 0, "Acquired locks for BACKUP STAGE " + "BLOCK_COMMIT"); + stage= HA_CLONE_STAGE_SNAPSHOT; + break; + case SUBCOM_EXEC_END: + /* The function could be invoked with SUBCOM_EXEC_END for error cleanup. + We need to check and unlock only if needed. */ + if (lock && m_acquired_backup_lock) + { + log_error(thd, false, 0, "Executing BACKUP STAGE END"); + err= clone_set_backup_stage(thd, END); + if (err) + { + err_msg= "Failed to release BACKUP LOCKS"; + goto err_exit; + } + log_error(thd, false, 0, "Released BACKUP LOCKS"); + } + stage= HA_CLONE_STAGE_END; + break; + case SUBCOM_MAX: + case SUBCOM_NONE: + err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Execution Request"); + log_error(get_thd(), false, err, "COM_EXECUTE"); + } +err_exit: + if (err_msg) + { + err= ER_INTERNAL_ERROR; + my_error(err, MYF(0), err_msg); + } + return err; +} + +int Server::execute_phase(Sub_Command sub_cmd) +{ + Ha_clone_stage exec_stage= HA_CLONE_STAGE_MAX; + int err= get_stage_and_lock(sub_cmd, exec_stage, m_is_master); + + if (!err && m_is_master) + err= send_locked(sub_cmd); + + if (!err) + { + Server_Cbk clone_callback(this); + err= hton_clone_copy(get_thd(), get_storage_vector(), m_tasks, exec_stage, + &clone_callback); + } + if (!err && sub_cmd == SUBCOM_EXEC_SNAPSHOT) + { + assert(m_is_master); + err= send_replication_state(); + } +#ifndef DBUG_OFF + if (sub_cmd == SUBCOM_EXEC_BLOCK_DDL) + DEBUG_SYNC_C("after_stage_block_ddl"); +#endif /* DBUG_OFF */ + log_error(get_thd(), false, err, sub_command_str(sub_cmd)); + return err; +} + +int Server::parse_command_buffer(uchar command, uchar *com_buf, size_t com_len, + bool &done) { + int err = 0; + auto com = static_cast(command); + done = false; + + switch (com) { + case COM_REINIT: + m_is_master = true; + err = init_storage(HA_CLONE_MODE_RESTART, com_buf, com_len); + log_error(get_thd(), false, err, "COM_REINIT: Storage Initialize"); + break; + + case COM_INIT: + m_is_master = true; + + /* Initialize storage, send locators and validating configurations. */ + err = init_storage(HA_CLONE_MODE_START, com_buf, com_len); + + log_error(get_thd(), false, err, "COM_INIT: Storage Initialize"); + break; + + case COM_ATTACH: + m_is_master = false; + err = init_storage(HA_CLONE_MODE_ADD_TASK, com_buf, com_len); + log_error(get_thd(), false, err, "COM_ATTACH: Storage Attach"); + break; + + case COM_EXECUTE: { + if (!m_storage_initialized) { + err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Execute request before Init"); + log_error(get_thd(), false, err, "COM_EXECUTE : Storage initialized"); + break; + } + + Sub_Command sub_cmd= SUBCOM_NONE; + err= deserialize_exec_buffer(com_buf, com_len, sub_cmd); + + if (err) + log_error(get_thd(), false, err, "COM_EXECUTE: Storage Execute"); + else + err= execute_phase(sub_cmd); + + break; + } + case COM_ACK: { + m_pfs_initialized = true; + int err_code = 0; + Locator loc = {nullptr, nullptr, 0}; + + Server_Cbk clone_callback(this); + + err = deserialize_ack_buffer(com_buf, com_len, &clone_callback, err_code, + &loc); + + if (err == 0) { + auto hton = loc.m_hton; + clone_callback.set_hton(hton); + + err = hton->clone_interface.clone_ack(get_thd(), loc.m_loc, + loc.m_loc_len, 0, err_code, + &clone_callback); + } + log_error(get_thd(), false, err, "COM_ACK: Storage Ack"); + break; + } + + case COM_EXIT: + if (m_storage_initialized) { + hton_clone_end(get_thd(), get_storage_vector(), m_tasks, 0); + m_storage_initialized = false; + } + done = true; + log_error(get_thd(), false, err, "COM_EXIT: Storage End"); + break; + + case COM_MAX: + [[fallthrough]]; + default: + /* purecov: begin deadcode */ + err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid request"); + break; + /* purecov: end */ + } + return (err); +} + +int Server::deserialize_exec_buffer(const uchar *exec_buf, size_t exec_len, + Sub_Command &sub_cmd) +{ + if (exec_len < 1 || static_cast(SUBCOM_MAX) <= *exec_buf) + { + my_error(ER_CLONE_PROTOCOL, MYF(0), "Wrong Clone RPC: EXEC Sub Command length"); + return ER_CLONE_PROTOCOL; + } + sub_cmd= static_cast(*exec_buf); + return 0; +} + +int Server::deserialize_ack_buffer(const uchar *ack_buf, size_t ack_len, + Ha_clone_cbk *cbk, int &err_code, + Locator *loc) { + size_t serialized_length = 0; + + const uchar *desc_ptr = nullptr; + uint desc_len = 0; + + /* Should not deserialize if less than the base length */ + if (ack_len < (4 + loc->serlialized_length())) { + goto err_end; + } + + /* Extract error code */ + err_code = uint4korr(ack_buf); + ack_buf += 4; + ack_len -= 4; + + /* Extract Locator */ + serialized_length = loc->deserialize(get_thd(), ack_buf); + + if (ack_len < serialized_length) { + goto err_end; + } + ack_buf += serialized_length; + ack_len -= serialized_length; + + /* Extract descriptor */ + if (ack_len < 4) { + goto err_end; + } + + desc_len = uint4korr(ack_buf); + ack_buf += 4; + ack_len -= 4; + + if (desc_len > 0) { + desc_ptr = ack_buf; + } + + cbk->set_data_desc(desc_ptr, desc_len); + + ack_len -= desc_len; + + if (ack_len == 0) { + return (0); + } + +err_end: + /* purecov: begin deadcode */ + my_error(ER_CLONE_PROTOCOL, MYF(0), "Wrong Clone RPC: Init ACK length"); + return (ER_CLONE_PROTOCOL); + /* purecov: end */ +} + +int Server::deserialize_init_buffer(const uchar *init_buf, size_t init_len) { + if (init_len < 8) { + goto err_end; + } + + /* Extract protocol version */ + m_protocol_version = uint4korr(init_buf); + if (m_protocol_version > CLONE_PROTOCOL_VERSION) { + m_protocol_version = CLONE_PROTOCOL_VERSION; + } + init_buf += 4; + init_len -= 4; + + /* Extract DDL timeout */ + { + const uint32_t client_ddl_timeout = uint4korr(init_buf); + init_buf += 4; + init_len -= 4; + + set_client_timeout(client_ddl_timeout); + } + + /* Initialize locators */ + while (init_len > 0) { + Locator loc = {nullptr, nullptr, 0}; + + /* Should not deserialize if less than the base length */ + if (init_len < loc.serlialized_length()) { + goto err_end; + } + + auto serialized_length = loc.deserialize(get_thd(), init_buf); + + init_buf += serialized_length; + + if (init_len < serialized_length) { + goto err_end; + } + + m_storage_vec.push_back(loc); + + init_len -= serialized_length; + } + + if (init_len == 0) { + return (0); + } + +err_end: + my_error(ER_CLONE_PROTOCOL, MYF(0), "Wrong Clone RPC: Init buffer length"); + + return (ER_CLONE_PROTOCOL); +} + +int Server::send_key_value(Command_Response rcmd, String_Key &key_str, + String_Key &val_str) { + /* Add length for key. */ + auto buf_len = key_str.length(); + buf_len += 4; + + const bool send_value = + (rcmd == COM_RES_CONFIG || rcmd == COM_RES_PLUGIN_V2 || + rcmd == COM_RES_CONFIG_V3); + + /** Add length for value. */ + if (send_value) { + buf_len += val_str.length(); + buf_len += 4; + } + /* Add length for response type. */ + ++buf_len; + + /* Allocate for response buffer */ + auto err = m_res_buff.allocate(buf_len); + auto buf_ptr = m_res_buff.m_buffer; + if (err != 0) { + return (true); + } + + /* Store response command */ + *buf_ptr = static_cast(rcmd); + ++buf_ptr; + + /* Store key */ + int4store(buf_ptr, key_str.length()); + buf_ptr += 4; + memcpy(buf_ptr, key_str.c_str(), key_str.length()); + buf_ptr += key_str.length(); + + /* Store Value */ + if (send_value) { + int4store(buf_ptr, val_str.length()); + buf_ptr += 4; + memcpy(buf_ptr, val_str.c_str(), val_str.length()); + } + err = clone_send_response(get_thd(), false, m_res_buff.m_buffer, buf_len); + + return (err); +} + +int Server::send_params() { + int err = 0; + + /* Send plugins */ + auto plugin_cbk = [](THD *, plugin_ref plugin, void *ctx)->my_bool { + auto server = static_cast(ctx); + + if (plugin == nullptr) { + return FALSE; + } + /* Send plugin name string */ + String_Key pstring(plugin_name(plugin)->str, plugin_name(plugin)->length); + + if (server->send_only_plugin_name()) { + auto err = server->send_key_value(COM_RES_PLUGIN, pstring, pstring); + return err != 0 ? TRUE : FALSE; + } + + /* Send plugin dynamic library name. */ + String_Key dstring; + + auto plugin_dl = plugin_dlib(plugin); + if (plugin_dl) + dstring.assign(plugin_dl->dl.str, plugin_dl->dl.length); + + auto err= server->send_key_value(COM_RES_PLUGIN_V2, pstring, dstring); + return err != 0 ? TRUE : FALSE; + }; + + /* Check only for plugins in active state - PLUGIN_IS_READY. We already have + backup lock here and no new plugins can be installed or uninstalled at this + point. However, there could be some left over plugins in PLUGIN_IS_DELETED + state which are uninstalled but not removed yet. */ + if (plugin_foreach(get_thd(), plugin_cbk, MYSQL_ANY_PLUGIN, this)) + { + err= ER_INTERNAL_ERROR; + my_error(err, MYF(0), "Clone error sending plugin information"); + return err; + } + + /* Send character sets and collations */ + String_Keys char_sets; + + err= clone_get_charsets(get_thd(), static_cast(&char_sets)); + if (err != 0) + return err; + + for (auto &element : char_sets) + { + err= send_key_value(COM_RES_COLLATION, element, element); + if (err != 0) + return err; + } + + /* Send configurations for validation. */ + err= send_configs(COM_RES_CONFIG); + + if (err != 0 || skip_other_configs()) + return err; + + /* Send other configurations required by recipient. */ + err= send_configs(COM_RES_CONFIG_V3); + + return err; +} + +int Server::send_configs(Command_Response rcmd) { + /** All configuration parameters to be validated. */ + Key_Values all_configs = {{"version", ""}, + {"version_compile_machine", ""}, + {"version_compile_os", ""}, + {"character_set_server", ""}, + {"character_set_filesystem", ""}, + {"collation_server", ""}, + {"innodb_page_size", ""}}; + + /** All other configuration required by recipient. */ + Key_Values other_configs = { + {"clone_donor_timeout_after_network_failure", ""}}; + + auto &configs = (rcmd == COM_RES_CONFIG_V3) ? other_configs : all_configs; + + int err= clone_get_configs(get_thd(), static_cast(&configs)); + + if (err != 0) { + return err; + } + + for (auto &key_val : configs) { + err = send_key_value(rcmd, key_val.first, key_val.second); + if (err != 0) { + break; + } + } + return err; +} + +int Server::send_locked(Sub_Command sub_cmd) +{ + assert(m_is_master); + size_t buf_len= 0; + + /* Add length for response type */ + ++buf_len; + + /* Add length for sub command */ + ++buf_len; + + auto err= m_res_buff.allocate(buf_len); + if (err != 0) + return err; + auto buf_ptr= m_res_buff.m_buffer; + + /* Store response command */ + *buf_ptr = static_cast(COM_RES_LOCKED); + ++buf_ptr; + + /* Store response command */ + *buf_ptr = static_cast(sub_cmd); + ++buf_ptr; + + err= clone_send_response(get_thd(), false, m_res_buff.m_buffer, buf_len); + return err; +} + +int Server::send_locators() { + /* Add length of protocol Version */ + auto buf_len = sizeof(m_protocol_version); + + /* Add length for response type */ + ++buf_len; + + /* Add SE and locator length */ + for (auto &loc : m_storage_vec) { + buf_len += loc.serlialized_length(); + } + + /* Allocate for response buffer */ + auto err = m_res_buff.allocate(buf_len); + auto buf_ptr = m_res_buff.m_buffer; + + if (err != 0) { + return (err); + } + + /* Store response command */ + *buf_ptr = static_cast(COM_RES_LOCS); + ++buf_ptr; + + /* Store version */ + int4store(buf_ptr, m_protocol_version); + buf_ptr += 4; + + /* Store SE information and Locators */ + for (auto &loc : m_storage_vec) { + buf_ptr += loc.serialize(buf_ptr); + } + + err = clone_send_response(get_thd(), false, m_res_buff.m_buffer, buf_len); + + return (err); +} + +int Server::send_descriptor(handlerton *hton, bool secure, uint loc_index, + const uchar *desc_buf, uint desc_len) { + /* Add data descriptor length */ + auto buf_len = desc_len; + + /* Add length for response type */ + ++buf_len; + + /* Add length for Storage Engine type */ + ++buf_len; + + /* Add length for Locator Index */ + ++buf_len; + + /* Allocate for response buffer */ + auto err = m_res_buff.allocate(buf_len); + + if (err != 0) { + return (err); + } + + auto buf_ptr = m_res_buff.m_buffer; + + /* Store response command */ + *buf_ptr = static_cast(COM_RES_DATA_DESC); + ++buf_ptr; + + /* Store Storage Engine type */ + *buf_ptr = static_cast(hton->db_type); + ++buf_ptr; + + /* Store Locator Index */ + *buf_ptr = static_cast(loc_index); + ++buf_ptr; + + /* Store Descriptor */ + memcpy(buf_ptr, desc_buf, desc_len); + + err = clone_send_response(get_thd(), secure, m_res_buff.m_buffer, buf_len); + + return (err); +} + +int Server_Cbk::send_descriptor() { + auto server = get_clone_server(); + + uint desc_len = 0; + auto desc = get_data_desc(&desc_len); + + auto err = server->send_descriptor(get_hton(), is_secure(), get_loc_index(), + desc, desc_len); + return (err); +} + +int Server_Cbk::file_cbk(Ha_clone_file from_file, uint len) { + auto server = get_clone_server(); + + /* Check if session is interrupted. */ + if (thd_killed(server->get_thd())) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return (ER_QUERY_INTERRUPTED); + } + + /* Add one byte for descriptor type */ + auto buf_len = len + 1; + auto buf_ptr = server->alloc_copy_buffer(buf_len + CLONE_OS_ALIGN); + + if (buf_ptr == nullptr) { + return (ER_OUTOFMEMORY); + } + + /* Store response command */ + auto data_ptr = buf_ptr + 1; + + /* Align buffer to CLONE_OS_ALIGN[4K] for O_DIRECT */ + data_ptr = clone_os_align(data_ptr); + buf_ptr = data_ptr - 1; + + *buf_ptr = static_cast(COM_RES_DATA); + + auto err = + clone_os_copy_file_to_buf(from_file, data_ptr, len, get_source_name()); + if (err != 0) { + return (err); + } + + /* Step 1: Send Descriptor */ + err = send_descriptor(); + + if (err != 0) { + return (err); + } + + /* Step 2: Send Data */ + err = clone_send_response(server->get_thd(), false, buf_ptr, buf_len); + + return (err); +} + +int Server_Cbk::buffer_cbk(uchar *from_buffer, uint buf_len) { + auto server = get_clone_server(); + + if (thd_killed(server->get_thd())) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return (ER_QUERY_INTERRUPTED); + } + + uchar *buf_ptr = nullptr; + uint total_len = 0; + + if (buf_len > 0) { + /* Add one byte for descriptor type */ + total_len = buf_len + 1; + buf_ptr = server->alloc_copy_buffer(total_len); + + if (buf_ptr == nullptr) { + return (ER_OUTOFMEMORY); + } + } + + /* Step 1: Send Descriptor */ + auto err = send_descriptor(); + + if (err != 0 || buf_len == 0) { + return (err); + } + + /* Step 2: Send Data */ + *buf_ptr = static_cast(COM_RES_DATA); + memcpy(buf_ptr + 1, from_buffer, static_cast(buf_len)); + + err = clone_send_response(server->get_thd(), false, buf_ptr, total_len); + + return (err); +} + +/* purecov: begin deadcode */ +int Server_Cbk::apply_file_cbk(Ha_clone_file to_file [[maybe_unused]]) { + assert(false); + my_error(ER_INTERNAL_ERROR, MYF(0), "Apply callback from Clone Server"); + return (ER_INTERNAL_ERROR); +} + +int Server_Cbk::apply_buffer_cbk(uchar *&to_buffer [[maybe_unused]], + uint &len [[maybe_unused]]) { + assert(false); + my_error(ER_INTERNAL_ERROR, MYF(0), "Apply callback from Clone Server"); + return (ER_INTERNAL_ERROR); +} +/* purecov: end */ +} // namespace myclone diff --git a/plugin/clone/src/clone_status.cc b/plugin/clone/src/clone_status.cc new file mode 100644 index 0000000000000..e7d489ddc30bc --- /dev/null +++ b/plugin/clone/src/clone_status.cc @@ -0,0 +1,917 @@ +/* Copyright (c) 2019, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone/src/clone_status.cc +Clone Plugin: Clone status as performance schema plugin table + +*/ + +#include "clone_status.h" +#include +#include +#include +// #include "my_io.h" +#include "clone.h" +#include "clone_client.h" + +#define SERVICE_TYPE_NO_CONST(X) void + +SERVICE_TYPE_NO_CONST(pfs_plugin_table_v1) *mysql_pfs_table = nullptr; +SERVICE_TYPE_NO_CONST(pfs_plugin_column_integer_v1) *mysql_pfscol_int = nullptr; +SERVICE_TYPE_NO_CONST(pfs_plugin_column_bigint_v1) *mysql_pfscol_bigint = + nullptr; +SERVICE_TYPE_NO_CONST(pfs_plugin_column_string_v2) *mysql_pfscol_string = + nullptr; +SERVICE_TYPE_NO_CONST(pfs_plugin_column_timestamp_v2) *mysql_pfscol_timestamp = + nullptr; +SERVICE_TYPE_NO_CONST(pfs_plugin_column_text_v1) *mysql_pfscol_text = nullptr; + +#define FILE_PREFIX "#" + +/** Clone directory */ +#define CLONE_FILES_DIR FILE_PREFIX "clone" FN_DIRSEP + +/** Clone recovery status. */ +const char CLONE_RECOVERY_FILE[] = + CLONE_FILES_DIR FILE_PREFIX "status_recovery"; + +/** Clone PFS view clone_status persister file */ +const char CLONE_VIEW_STATUS_FILE[] = CLONE_FILES_DIR FILE_PREFIX "view_status"; + +/** Clone PFS view clone_progress persister file */ +const char CLONE_VIEW_PROGRESS_FILE[] = + CLONE_FILES_DIR FILE_PREFIX "view_progress"; + +#ifdef NEVER +template +static bool acquire_service(T &service, const char *name) +{ + my_h_service mysql_service; + if (mysql_service_registry->acquire(name, &mysql_service)) + return true; + + service = reinterpret_cast(mysql_service); + return false; +} + +#define ACQUIRE_SERVICE(service, name) \ + if (0 != acquire_service(service, name)) { \ + return (true); \ + } + +#define RELEASE_SERVICE(service) \ + if (service != nullptr) { \ + mysql_service_registry->release(reinterpret_cast(service)); \ + service = nullptr; \ + } +#endif // NEVER + +/* Namespace for all clone data types */ +namespace myclone +{ +/* PFS proxy table for clone status. */ +Status_pfs g_status_table = {}; + +/* PFS proxy table for clone progress. */ +Progress_pfs g_progress_table = {}; + +/** PFS proxy table array. */ +// static PFS_engine_table_share_proxy *pfs_proxy_tables[2] = {nullptr, nullptr}; + +/** All CLONE state names. */ +std::array Table_pfs::s_state_names = {}; + +/** All CLONE stage names. */ +std::array Table_pfs::s_stage_names = {}; + +/** Clone client status data. */ +Status_pfs::Data Client::s_status_data = {}; + +/** Clone client progress data. */ +Progress_pfs::Data Client::s_progress_data = {}; + +/** Mutex to protect status and progress data. */ +mysql_mutex_t Client::s_table_mutex; + +/** Number of concurrent clone clients. */ +uint32_t Client::s_num_clones = 0; + +int Table_pfs::create_proxy_tables() +{ + // auto thd = current_thd; + // if (mysql_pfs_table == nullptr || thd == nullptr) + // if (!thd) + // return 1; + + Client::init_pfs(); + // pfs_proxy_tables[0] = g_status_table.get_proxy_share(); + // pfs_proxy_tables[1] = g_progress_table.get_proxy_share(); + return 0; // mysql_pfs_table->add_tables(pfs_proxy_tables, 2); +} + +void Table_pfs::drop_proxy_tables() +{ + // if (mysql_pfs_table != nullptr) + // return; + + // static_cast(mysql_pfs_table->delete_tables(pfs_proxy_tables, 2)); + Client::uninit_pfs(); +} + +bool Table_pfs::acquire_services() +{ + /* Get Table service. */ + // ACQUIRE_SERVICE(mysql_pfs_table, "pfs_plugin_table_v1") + /* Get column services. */ + // ACQUIRE_SERVICE(mysql_pfscol_int, "pfs_plugin_column_integer_v1") + // ACQUIRE_SERVICE(mysql_pfscol_bigint, "pfs_plugin_column_bigint_v1") + // ACQUIRE_SERVICE(mysql_pfscol_string, "pfs_plugin_column_string_v2") + // ACQUIRE_SERVICE(mysql_pfscol_timestamp, "pfs_plugin_column_timestamp_v2") + // ACQUIRE_SERVICE(mysql_pfscol_text, "pfs_plugin_column_text_v1") + + auto err = create_proxy_tables(); + if (err != 0) + return true; + + init_state_names(); + return false; +} + +void Table_pfs::init_state_names() +{ + /* Initialise state names. Defaults to nullptr. */ + uint32_t index = 0; + for (auto &state_name : s_state_names) { + auto state_index = static_cast(index); + switch (state_index) + { + case STATE_NONE: + state_name = "Not Started"; + break; + case STATE_STARTED: + state_name = "In Progress"; + break; + case STATE_SUCCESS: + state_name = "Completed"; + break; + case STATE_FAILED: + state_name = "Failed"; + break; + default: + assert(false); + } + ++index; + } + /* Initialise stage names. Defaults to nullptr. */ + index = 0; + for (auto &stage_name : s_stage_names) + { + auto stage_index = static_cast(index); + switch (stage_index) { + case STAGE_NONE: + stage_name = "None"; + break; + case STAGE_CLEANUP: + stage_name = "DROP DATA"; + break; + case STAGE_FILE_COPY: + stage_name = "FILE COPY"; + break; + case STAGE_PAGE_COPY: + stage_name = "PAGE COPY"; + break; + case STAGE_REDO_COPY: + stage_name = "REDO COPY"; + break; + case STAGE_FILE_SYNC: + stage_name = "FILE SYNC"; + break; + case STAGE_RESTART: + stage_name = "RESTART"; + break; + case STAGE_RECOVERY: + stage_name = "RECOVERY"; + break; + default: + assert(false); + } + ++index; + } +} + +void Table_pfs::release_services() +{ + drop_proxy_tables(); + // RELEASE_SERVICE(mysql_pfs_table); + // RELEASE_SERVICE(mysql_pfscol_int); + // RELEASE_SERVICE(mysql_pfscol_bigint); + // RELEASE_SERVICE(mysql_pfscol_string); + // RELEASE_SERVICE(mysql_pfscol_timestamp); + // RELEASE_SERVICE(mysql_pfscol_text); +} + +#ifdef NEVER +static int cbk_rnd_init(PSI_table_handle *handle, bool) +{ + auto table = reinterpret_cast(handle); + return table->rnd_init(); +} + +static int cbk_rnd_next(PSI_table_handle *handle) +{ + auto table = reinterpret_cast(handle); + return table->rnd_next(); +} + +static int cbk_rnd_pos(PSI_table_handle *handle) +{ + auto table = reinterpret_cast(handle); + return table->rnd_pos(); +} + +static void cbk_reset_pos(PSI_table_handle *handle) +{ + auto table = reinterpret_cast(handle); + table->reset_pos(); +} + +static int cbk_read_column(PSI_table_handle *handle, PSI_field *field, + uint32_t index) +{ + auto table = reinterpret_cast(handle); + return table->read_column_value(field, index); +} + +static void cbk_close_table(PSI_table_handle *handle) +{ + auto table = reinterpret_cast(handle); + table->close(); +} + +/** Open clone status table for PFS. +@param[out] row_pos address of row position +@return clone status table. */ +static Status_pfs *open_status_table(uint32_t **row_pos) +{ + uint32_t *pos_addr = g_status_table.get_position_address(); + *row_pos = pos_addr; + return &g_status_table; +} + +/** Open clone progress table for PFS. +@param[out] row_pos address of row position +@return clone progress table. */ +static Progress_pfs *open_progress_table(uint32_t **row_pos) +{ + uint32_t *pos_addr = g_progress_table.get_position_address(); + *row_pos = pos_addr; + return &g_progress_table; +} +#endif // NEVER + +Table_pfs::Table_pfs(uint32_t num_rows) + : m_rows(num_rows), m_position(), m_empty(true) +{ + /* Must set for each table separately in derived classes. */ + // m_pfs_table.m_table_name = ""; + // m_pfs_table.m_table_name_length = 0; + // m_pfs_table.m_table_definition = ""; + + /* Table information common for all. */ + // m_pfs_table.m_ref_length = sizeof(uint32_t); + // m_pfs_table.m_acl = READONLY; + // m_pfs_table.delete_all_rows = nullptr; + + /* Initialize proxy table access methods. */ + // auto &proxy_table = m_pfs_table.m_proxy_engine_table; + + /* Table open and close method. Open method must be set + separately in each derived class. */ + // proxy_table.open_table = nullptr; + // proxy_table.close_table = cbk_close_table; + + /* Table scan methods. */ + // proxy_table.rnd_init = cbk_rnd_init; + // proxy_table.rnd_next = cbk_rnd_next; + // proxy_table.rnd_pos = cbk_rnd_pos; + + /* Read operation. */ + // proxy_table.read_column_value = cbk_read_column; + // proxy_table.reset_position = cbk_reset_pos; + + /* No index scan. */ + // proxy_table.index_init = nullptr; + // proxy_table.index_read = nullptr; + // proxy_table.index_next = nullptr; + + /* No write operation. */ + // proxy_table.write_column_value = nullptr; + // proxy_table.write_row_values = nullptr; + // proxy_table.update_column_value = nullptr; + // proxy_table.update_row_values = nullptr; + // proxy_table.delete_row_values = nullptr; +} + +#ifdef NEVER +static unsigned long long cbk_status_row_count() +{ + return Status_pfs::S_NUM_ROWS; +} + +static PSI_table_handle *cbk_status_open_table(PSI_pos **pos) +{ + auto row_pos = reinterpret_cast(pos); + auto table = open_status_table(row_pos); + auto handle = reinterpret_cast(table); + return handle; +} +#endif // NEVER + +Status_pfs::Status_pfs() : Table_pfs(S_NUM_ROWS) +{ + // auto table = get_proxy_share(); + // table->m_table_name = "clone_status"; + // table->m_table_name_length = strlen(table->m_table_name); + // table->m_table_definition = + // "`ID` int," + // "`PID` int," + // "`STATE` char(16)," + // "`BEGIN_TIME` timestamp(3) NULL," + // "`END_TIME` timestamp(3) NULL," + // "`SOURCE` varchar(512)," + // "`DESTINATION` varchar(512)," + // "`ERROR_NO` int," + // "`ERROR_MESSAGE` varchar(512)," + // "`BINLOG_FILE` varchar(512)," + // "`BINLOG_POSITION` bigint," + // "`GTID_EXECUTED` longtext"; + // table->get_row_count = cbk_status_row_count; + + // auto &proxy_table = table->m_proxy_engine_table; + // proxy_table.open_table = cbk_status_open_table; +} + +int Status_pfs::rnd_init() +{ + Client::copy_pfs_data(m_data); + Table_pfs::init_position(m_data.m_id); + return 0; +} + +#ifdef NEVER +int Status_pfs::read_column_value(PSI_field *field, uint32_t index) +{ + assert(!is_empty()); + PSI_uint int_value; + PSI_ulonglong bigint_value; + + /* Return NULL if cursor is positioned at beginning or end. */ + auto row_index = get_position(); + const bool is_null = (row_index == 0 || row_index > S_NUM_ROWS); + + switch (index) { + case 0: /* ID: Clone ID */ + int_value.val = m_data.m_id; + int_value.is_null = is_null; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + case 1: /* PID: Process List ID */ + int_value.val = m_data.m_pid; + int_value.is_null = is_null; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + case 2: /* STATE */ + mysql_pfscol_string->set_char_utf8mb4( + field, s_state_names[m_data.m_state], + strlen(s_state_names[m_data.m_state])); + break; + case 3: /* BEGIN_TIME */ + mysql_pfscol_timestamp->set2(field, is_null ? 0 : m_data.m_start_time); + break; + case 4: /* END_TIME */ + mysql_pfscol_timestamp->set2(field, is_null ? 0 : m_data.m_end_time); + break; + case 5: /* SOURCE */ + mysql_pfscol_string->set_varchar_utf8mb4( + field, is_null ? nullptr : m_data.m_source); + break; + case 6: /* DESTINATION */ + mysql_pfscol_string->set_varchar_utf8mb4( + field, is_null ? nullptr : m_data.m_destination); + break; + case 7: /* ERROR_NUMBER */ + int_value.val = m_data.m_error_number; + int_value.is_null = is_null; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + case 8: /* ERROR_MESSAGE */ + mysql_pfscol_string->set_varchar_utf8mb4( + field, is_null ? nullptr : m_data.m_error_mesg); + break; + case 9: /* BINLOG_FILE */ { + const size_t dir_len = dirname_length(m_data.m_binlog_file); + mysql_pfscol_string->set_varchar_utf8mb4( + field, is_null ? nullptr : m_data.m_binlog_file + dir_len); + } break; + case 10: /* BINLOG_POSITION */ + bigint_value.val = m_data.m_binlog_pos; + bigint_value.is_null = is_null; + mysql_pfscol_bigint->set_unsigned(field, bigint_value); + break; + case 11: /* GTID_EXECUTED */ { + int length = is_null ? 0 : m_data.m_gtid_string.length(); + mysql_pfscol_text->set( + field, is_null ? nullptr : m_data.m_gtid_string.c_str(), length); + } break; + default: /* purecov: inspected */ + assert(false); /* purecov: inspected */ + } + return (0); +} +#endif // NEVER + +void Status_pfs::Data::write(bool write_error) +{ + std::string file_name; + /* Append data directory if cloning to different place. */ + if (!is_local()) + { + file_name.assign(m_destination); + file_name.append(FN_DIRSEP); + file_name.append(CLONE_VIEW_STATUS_FILE); + } + else + file_name.assign(CLONE_VIEW_STATUS_FILE); + + std::ofstream status_file; + status_file.open(file_name, std::ofstream::out | std::ofstream::trunc); + if (!status_file.is_open()) + return; + + auto state = static_cast(m_state); + /* Write state columns. */ + status_file << state << " " << m_id << std::endl; + + /* Write time columns. */ + status_file << m_start_time << " " << m_end_time << std::endl; + + /* Write source string. */ + status_file << m_source << std::endl; + + /* Write error columns. */ + if (write_error) + { + status_file << m_error_number << std::endl; + status_file << m_error_mesg << std::endl; + } + else + { + /* Write interrupt error, for possible crash. */ + status_file << ER_QUERY_INTERRUPTED << std::endl; + status_file << "Query execution was interrupted" << std::endl; + } + /* Write binary log information. */ + status_file << m_binlog_file << std::endl; + status_file << m_binlog_pos << std::endl; + status_file << m_gtid_string << std::endl; + status_file.close(); +} + +void Status_pfs::Data::read() +{ + std::string file_name; + file_name.assign(CLONE_VIEW_STATUS_FILE); + + std::ifstream status_file; + status_file.open(file_name, std::ifstream::in); + if (!status_file.is_open()) + return; + + /* Set fixed data. */ + m_pid = 0; + strncpy(m_destination, &g_local_string[0], sizeof(m_destination) - 1); + + std::string file_line; + int line_number = 0; + uint32_t state = 0; + /* loop through the lines and extract status information. */ + while (std::getline(status_file, file_line)) + { + ++line_number; + std::stringstream file_data(file_line, std::ifstream::in); + switch (line_number) + { + case 1: + /* Read state columns. */ + file_data >> state >> m_id; + m_state = STATE_NONE; + if (state < static_cast(NUM_STATES)) + m_state = static_cast(state); + break; + case 2: + /* Read time columns. */ + file_data >> m_start_time >> m_end_time; + break; + case 3: + /* read source string */ + strncpy(m_source, file_line.c_str(), sizeof(m_source) - 1); + break; + case 4: + /* Read error number. */ + file_data >> m_error_number; + break; + case 5: + /* read error string */ + strncpy(m_error_mesg, file_line.c_str(), sizeof(m_error_mesg) - 1); + break; + case 6: + /* Read binary log file name. */ + strncpy(m_binlog_file, file_line.c_str(), sizeof(m_binlog_file) - 1); + break; + case 7: + /* Read binary log position. */ + file_data >> m_binlog_pos; + break; + case 8: + /* Read GTID_EXECUTED. */ + m_gtid_string.assign(file_data.str()); + break; + default: + m_gtid_string.append("\n"); + m_gtid_string.append(file_data.str()); + break; + } + } + status_file.close(); +} + +void Status_pfs::Data::recover() +{ + const std::string file_name(CLONE_RECOVERY_FILE); + std::ifstream recovery_file; + recovery_file.open(file_name, std::ifstream::in); + if (!recovery_file.is_open()) + return; + + std::string file_line; + int line_number = 0; + uint64_t recovery_end_time = 0; + /* loop through the lines and extract binary log information. */ + while (std::getline(recovery_file, file_line)) + { + ++line_number; + std::stringstream rec_data(file_line, std::ifstream::in); + switch (line_number) + { + case 1: + break; + case 2: + rec_data >> recovery_end_time; + break; + case 3: + /* Read binary log file name. */ + strncpy(m_binlog_file, file_line.c_str(), sizeof(m_binlog_file) - 1); + break; + case 4: + /* Read binary log position. */ + rec_data >> m_binlog_pos; + break; + case 5: + /* Read GTID_EXECUTED. */ + m_gtid_string.assign(rec_data.str()); + break; + default: + m_gtid_string.append("\n"); + m_gtid_string.append(rec_data.str()); + break; + } + } + recovery_file.close(); + std::remove(CLONE_RECOVERY_FILE); + + if (recovery_end_time == 0) + { + m_error_number = ER_INTERNAL_ERROR; + strncpy(m_error_mesg, + "Recovery failed. Please Retry Clone. " + "For details, look into server error log.", + sizeof(m_error_mesg) - 1); + m_state = STATE_FAILED; + } + else + { + /* Recovery finished successfully. Reset state and error. */ + m_state = STATE_SUCCESS; + m_error_number = 0; + memset(m_error_mesg, 0, sizeof(m_error_mesg)); + } + /* Update end time for clone operation. */ + m_end_time = recovery_end_time; + + /* Write back to the file after updating binary log positions. */ + write(true); +} + +#ifdef NEVER +static unsigned long long cbk_progress_row_count() +{ + return (Progress_pfs::S_NUM_ROWS); +} + +static PSI_table_handle *cbk_progress_open_table(PSI_pos **pos) +{ + auto row_pos = reinterpret_cast(pos); + auto table = open_progress_table(row_pos); + auto handle = reinterpret_cast(table); + return handle; +} +#endif // NEVER + +Progress_pfs::Progress_pfs() : Table_pfs(S_NUM_ROWS) +{ +// auto table = get_proxy_share(); +// table->m_table_name = "clone_progress"; +// table->m_table_name_length = strlen(table->m_table_name); +// table->m_table_definition = +// "`ID` int," +// "`STAGE` char(32)," +// "`STATE` char(16)," +// "`BEGIN_TIME` timestamp(6) NULL," +// "`END_TIME` timestamp(6) NULL," +// "`THREADS` int," +// "`ESTIMATE` bigint," +// "`DATA` bigint," +// "`NETWORK` bigint," +// "`DATA_SPEED` int," +// "`NETWORK_SPEED` int"; +// table->get_row_count = cbk_progress_row_count; + +// auto &proxy_table = table->m_proxy_engine_table; +// proxy_table.open_table = cbk_progress_open_table; +} + +int Progress_pfs::rnd_init() +{ + Client::copy_pfs_data(m_data); + Table_pfs::init_position(m_data.m_id); + return 0; +} + +#ifdef NEVER +int Progress_pfs::read_column_value(PSI_field *field, uint32_t index) +{ + assert(!is_empty()); + PSI_uint int_value; + PSI_ulonglong bigint_value; + + /* Return NULL if cursor is positioned at beginning or end. */ + auto row_index = get_position(); + const bool is_null = (row_index == 0 || row_index > S_NUM_ROWS); + + switch (index) { + case 0: /* ID: Clone ID */ + int_value.val = m_data.m_id; + int_value.is_null = false; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + case 1: /* STAGE */ + mysql_pfscol_string->set_char_utf8mb4( + field, s_stage_names[row_index], + is_null ? 0 : strlen(s_stage_names[row_index])); + break; + case 2: /* STATE */ { + auto name_index = m_data.m_states[row_index]; + mysql_pfscol_string->set_char_utf8mb4( + field, s_state_names[name_index], + is_null ? 0 : strlen(s_state_names[name_index])); + break; + } + case 3: /* BEGIN_TIME */ + mysql_pfscol_timestamp->set2( + field, is_null ? 0 : m_data.m_start_time[row_index]); + break; + case 4: /* END_TIME */ + mysql_pfscol_timestamp->set2(field, + is_null ? 0 : m_data.m_end_time[row_index]); + break; + case 5: /* THREADS */ + int_value.val = m_data.m_threads[row_index]; + int_value.is_null = is_null; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + case 6: /* ESTIMATE */ + bigint_value.val = m_data.m_estimate[row_index]; + bigint_value.is_null = is_null; + mysql_pfscol_bigint->set_unsigned(field, bigint_value); + break; + case 7: /* DATA */ + bigint_value.val = m_data.m_complete[row_index]; + bigint_value.is_null = is_null; + mysql_pfscol_bigint->set_unsigned(field, bigint_value); + break; + case 8: /* NETWORK */ + bigint_value.val = m_data.m_network[row_index]; + bigint_value.is_null = is_null; + mysql_pfscol_bigint->set_unsigned(field, bigint_value); + break; + case 9: /* DATA_SPEED */ + int_value.val = (m_data.m_states[row_index] == STATE_STARTED) + ? m_data.m_data_speed + : 0; + int_value.is_null = is_null; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + case 10: /* NETWORK_SPEED */ + int_value.val = (m_data.m_states[row_index] == STATE_STARTED) + ? m_data.m_network_speed + : 0; + int_value.is_null = is_null; + mysql_pfscol_int->set_unsigned(field, int_value); + break; + default: /* purecov: inspected */ + assert(false); /* purecov: inspected */ + } + return 0; +} +#endif // NEVER + +void Progress_pfs::Data::write(const char *data_dir) { + std::string file_name; + + if (data_dir != nullptr) + { + file_name.assign(data_dir); + file_name.append(FN_DIRSEP); + file_name.append(CLONE_VIEW_PROGRESS_FILE); + } else + file_name.assign(CLONE_VIEW_PROGRESS_FILE); + + std::ofstream status_file; + status_file.open(file_name, std::ofstream::out | std::ofstream::trunc); + if (!status_file.is_open()) + return; + /* Write elements common to all stages. */ + status_file << m_id << std::endl; + + Clone_stage cur_stage = STAGE_NONE; + next_stage(cur_stage); + + /* Loop through all stages. */ + while (cur_stage != STAGE_NONE) + { + auto cur_index = static_cast(cur_stage); + Clone_state state = m_states[cur_index]; + /* Unfinished states are marked failed, to indicate error after crash. */ + if (state == STATE_STARTED) + state = STATE_FAILED; + + status_file << state << " " << m_threads[cur_index] << " " + << m_start_time[cur_index] << " " << m_end_time[cur_index] + << " " << m_estimate[cur_index] << " " << m_complete[cur_index] + << " " << m_network[cur_index] << std::endl; + + next_stage(cur_stage); + } + status_file.close(); +} + +void Progress_pfs::Data::read() +{ + std::string file_name; + file_name.assign(CLONE_VIEW_PROGRESS_FILE); + + std::ifstream status_file; + status_file.open(file_name, std::ifstream::in); + if (!status_file.is_open()) { + return; + } + + bool read_common = false; + Clone_stage cur_stage = STAGE_NONE; + next_stage(cur_stage); + + std::string file_line; + /* loop through the lines and extract status information. */ + while (std::getline(status_file, file_line)) + { + std::stringstream file_data(file_line, std::ifstream::in); + /* Read information common to all stages. */ + if (!read_common) + { + file_data >> m_id; + read_common = true; + continue; + } + auto cur_index = static_cast(cur_stage); + uint32_t state = 0; + file_data >> state >> m_threads[cur_index] >> m_start_time[cur_index] >> + m_end_time[cur_index] >> m_estimate[cur_index] >> + m_complete[cur_index] >> m_network[cur_index]; + + m_states[cur_index] = static_cast(state); + next_stage(cur_stage); + + if (cur_stage == STAGE_NONE) + break; + } + status_file.close(); + + /* Update recovery status. */ + file_name.assign(CLONE_RECOVERY_FILE); + status_file.open(file_name, std::ifstream::in); + + if (!status_file.is_open()) + return; + + int line_number = 0; + /* If recovery end time is not written, recovery is not successful. */ + uint64_t recovery_end_time = 0; + + /* loop through the lines and extract binary log information. */ + while (std::getline(status_file, file_line)) + { + ++line_number; + std::stringstream rec_data(file_line, std::ifstream::in); + switch (line_number) + { + case 1: + /* Read recovery start time. */ + rec_data >> m_start_time[STAGE_RECOVERY]; + /* Handle the case when server crashed after successfully completing + clone but before updating PFS data. */ + if (m_end_time[STAGE_FILE_SYNC] == 0 || + m_states[STAGE_FILE_SYNC] != STATE_SUCCESS) + { + m_end_time[STAGE_FILE_SYNC] = m_start_time[STAGE_FILE_SYNC]; + m_states[STAGE_FILE_SYNC] = STATE_SUCCESS; + } + /* Set server restart stage data. */ + m_start_time[STAGE_RESTART] = m_end_time[STAGE_FILE_SYNC]; + m_end_time[STAGE_RESTART] = m_start_time[STAGE_RECOVERY]; + m_states[STAGE_RESTART] = STATE_SUCCESS; + break; + case 2: + /* Read recovery end time. */ + rec_data >> recovery_end_time; + break; + default: + break; + } + if (line_number >= 2) + break; + } + status_file.close(); + + m_end_time[STAGE_RECOVERY] = recovery_end_time; + m_states[STAGE_RECOVERY] = + (m_end_time[STAGE_RECOVERY] == 0) ? STATE_FAILED : STATE_SUCCESS; + + /* Write back to the file after updating recovery details. */ + write(nullptr); +} + +void log_error(THD *thd, bool is_client, int32_t error, + const char *message_start) +{ + if (error == 0) + { + LogPluginErr(INFORMATION_LEVEL, + is_client ? ER_CLONE_CLIENT_TRACE : ER_CLONE_SERVER_TRACE, + message_start); + return; + } + + uint32_t thd_error = 0; + const char *error_mesg = nullptr; + clone_get_error(thd, &thd_error, &error_mesg); + char info_mesg[256]; + snprintf(info_mesg, 256, "%s: error: %d: %s", message_start, error, + error_mesg != nullptr ? error_mesg : ""); + + LogPluginErr(INFORMATION_LEVEL, + is_client ? ER_CLONE_CLIENT_TRACE : ER_CLONE_SERVER_TRACE, + info_mesg); +} + +} // namespace myclone diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 175f2abe5720f..8f1a70906819e 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -95,6 +95,7 @@ SET (SQL_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/yy_mariadb.cc ${CMAKE_CURRENT_BINARY_DIR}/yy_oracle.cc ../sql-common/client.c + clone_handler.cc cset_narrowing.cc compat56.cc derror.cc des_key_file.cc discover.cc ../sql-common/errmsg.c @@ -121,6 +122,7 @@ SET (SQL_SOURCE opt_vcol_substitution.cc ../sql-common/pack.c parse_file.cc password.c procedure.cc protocol.cc records.cc repl_failsafe.cc rpl_filter.cc + service_clone_protocol.cc session_tracker.cc set_var.cc slave.cc sp.cc sp_cache.cc sp_head.cc sp_pcontext.cc @@ -241,13 +243,19 @@ STATIC_ONLY NOT_EMBEDDED) ADD_LIBRARY(sql STATIC ${SQL_SOURCE}) MAYBE_DISABLE_IPO(sql) DTRACE_INSTRUMENT(sql) -TARGET_LINK_LIBRARIES(sql - mysys mysys_ssl dbug strings vio pcre2-8 - tpool - online_alter_log - ${LIBWRAP} ${LIBCRYPT} ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} - ${SSL_LIBRARIES} - ${LIBSYSTEMD}) +# Link libstdc++fs for GNU compiler versions 5-8 +SET(SQL_LINK_LIBRARIES + mysys mysys_ssl dbug strings vio pcre2-8 + tpool + online_alter_log + ${LIBWRAP} ${LIBCRYPT} ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} + ${SSL_LIBRARIES} + ${LIBSYSTEMD}) +IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.0") + LIST(APPEND SQL_LINK_LIBRARIES stdc++fs) +ENDIF() + +TARGET_LINK_LIBRARIES(sql ${SQL_LINK_LIBRARIES}) IF(TARGET pcre2) ADD_DEPENDENCIES(sql pcre2) ENDIF() diff --git a/sql/clone_handler.cc b/sql/clone_handler.cc new file mode 100644 index 0000000000000..947e0dd0061cc --- /dev/null +++ b/sql/clone_handler.cc @@ -0,0 +1,580 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "clone_handler.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "my_global.h" +#include "my_dir.h" +#include "my_sys.h" +#include "mysql/plugin.h" +#include "mysql/plugin_clone.h" +#include "mysql/psi/mysql_file.h" +#include "mysql/psi/mysql_thread.h" +#include "mysqld_error.h" +#include "mysqld.h" +#include "sql_class.h" +#include "sql_parse.h" +#include "sql_plugin.h" // plugin_unlock +#include "sql_string.h" // to_lex_cstring +#include "sql_table.h" // filename_to_tablename +#include "violite.h" + +class THD; + +/** Clone handler global */ +Clone_handler *clone_handle= nullptr; + +/** Clone plugin name */ +const char *clone_plugin_nm= "clone"; + +bool Clone_handler::get_donor_error(int &error, const char *&message) +{ + error= 0; + message= nullptr; + + /* Try to get current THD handle. */ + THD *thd= current_thd; + if (thd == nullptr) + return false; + + /* Check if DA exists. */ + auto da= thd->get_stmt_da(); + if (da == nullptr || !da->is_error()) + return false; + + if (da->sql_errno() != ER_CLONE_DONOR) + return false; + + /* Assign current error from DA */ + error= da->sql_errno(); + message= da->message(); + + /* Parse and find out donor error and message. */ + size_t err_pos= 0; + const std::string msg_string(message); + + while (!std::isdigit(message[err_pos])) + { + /* Find position of next ":". */ + err_pos= msg_string.find(": ", err_pos); + + /* No more separator, return. */ + if (err_pos == std::string::npos) + { + assert(false); + return false; + } + /* Skip ":" and space. */ + err_pos+= 2; + } + + error= std::atoi(message + err_pos); + + if (error != 0) + { + err_pos= msg_string.find(": ", err_pos); + /* Should find the error message following the error code. */ + if (err_pos == std::string::npos) + { + assert(false); + return false; + } + /* Skip ":" and space. */ + err_pos+= 2; + message= message + err_pos; + } + return true; +} + +int Clone_handler::clone_local(THD *thd, const char *data_dir) +{ + char dir_name[FN_REFLEN]; + + int error= validate_dir(data_dir, dir_name); + + if (!error) + error= m_plugin_handle->clone_local(thd, dir_name); + + return error; +} + +int Clone_handler::clone_remote_client(THD *thd, const char *remote_host, + uint remote_port, const char *remote_user, const char *remote_passwd, + const char *data_dir, int ssl_mode) +{ + int error= 0; + char dir_name[FN_REFLEN]; + char *dir_ptr= nullptr; + + /* NULL when clone replaces current data directory. */ + if (data_dir != nullptr) { + error= validate_dir(data_dir, dir_name); + dir_ptr= &dir_name[0]; + } + + if (error) + return error; + + /* NULL data directory implies we are replacing current data directory + for provisioning this node. We never set it back to false only in case + of error otherwise the server would shutdown or restart at the end of + operation. */ + const bool provisioning= (data_dir == nullptr); + if (provisioning) + ++s_provision_in_progress; + + error= m_plugin_handle->clone_client(thd, remote_host, remote_port, + remote_user, remote_passwd, dir_ptr, ssl_mode); + + if (error != 0 && provisioning) + --s_provision_in_progress; + + return error; +} + +int Clone_handler::clone_remote_server(THD *thd, MYSQL_SOCKET socket) +{ + auto err= m_plugin_handle->clone_server(thd, socket); + return err; +} + +int Clone_handler::init() +{ + const char* name= m_plugin_name.c_str(); + size_t name_length= name ? strlen(name) : 0; + LEX_CSTRING cstr= {name, name_length}; + + plugin_ref plugin= my_plugin_lock_by_name(nullptr, &cstr, + MariaDB_CLONE_PLUGIN); + if (!plugin) + { + m_plugin_handle= nullptr; + const char* mesg= my_get_err_msg(ER_CLONE_PLUGIN_NOT_LOADED_TRACE); + my_printf_error(ER_CLONE_PLUGIN_NOT_LOADED_TRACE, "%s", ME_ERROR_LOG_ONLY, + mesg); + return 1; + } + + m_plugin_handle= (Mysql_clone *)plugin_decl(plugin)->info; + plugin_unlock(nullptr, plugin); + + if (opt_bootstrap) + /* Inform that database initialization in progress. */ + return ER_SERVER_SHUTDOWN; + + return 0; +} + +int Clone_handler::validate_dir(const char *in_dir, char *out_dir) +{ + MY_STAT stat_info; + + /* Verify that it is absolute path. */ + if (!test_if_hard_path(in_dir)) + { + my_error(ER_WRONG_VALUE, MYF(0), "path", in_dir); + return ER_WRONG_VALUE; + } + + /* Verify that the length is not too long. */ + if (strlen(in_dir) >= FN_REFLEN - 1) + { + my_error(ER_PATH_LENGTH, MYF(0), "DATA DIRECTORY"); + return ER_PATH_LENGTH; + } + + /* Convert the path to native os format. */ + convert_dirname(out_dir, in_dir, nullptr); + + /* Check if the data directory exists already. */ + if (mysql_file_stat(key_file_misc, out_dir, &stat_info, MYF(0))) + { + my_error(ER_DB_CREATE_EXISTS, MYF(0), in_dir); + return ER_DB_CREATE_EXISTS; + } + + /* Check if path is within current data directory */ + char tmp_dir[FN_REFLEN + 1]; + size_t length; + + strncpy(tmp_dir, out_dir, FN_REFLEN); + length= strlen(out_dir); + + /* Loop and remove all non-existent directories from the tail */ + while (length) + { + /* Check if directory exists. */ + if (mysql_file_stat(key_file_misc, tmp_dir, &stat_info, MYF(0))) + { + /* Check if the path is not within data directory. */ + if (test_if_data_home_dir(tmp_dir)) + { + my_error(ER_PATH_IN_DATADIR, MYF(0), in_dir); + return ER_PATH_IN_DATADIR; + } + break; + } + + size_t new_length; + tmp_dir[length - 1]= '\0'; + + /* Remove the last directory separator from string */ + dirname_part(tmp_dir, tmp_dir, &new_length); + + /* length must always decrease for the loop to terminate */ + if (length <= new_length) + { + assert(false); + break; + } + + length= new_length; + } + return 0; +} + +int clone_handle_create(const char *plugin_name) +{ + if (clone_handle) + { + const char* mesg= my_get_err_msg(ER_CLONE_HANDLER_EXIST_TRACE); + my_printf_error(ER_CLONE_HANDLER_EXIST_TRACE, "%s", ME_ERROR_LOG_ONLY, + mesg); + return 1; + } + + clone_handle= new Clone_handler(plugin_name); + + if (!clone_handle) + { + const char* mesg= my_get_err_msg(ER_CLONE_CREATE_HANDLER_FAIL_TRACE); + my_printf_error(ER_CLONE_CREATE_HANDLER_FAIL_TRACE, "%s", + ME_ERROR_LOG_ONLY, mesg); + return 1; + } + + return clone_handle->init(); +} + +int clone_handle_check_drop(MYSQL_PLUGIN plugin_info) +{ + auto plugin= static_cast(plugin_info); + int error= 0; + + mysql_mutex_lock(&LOCK_plugin); + assert(plugin->state == PLUGIN_IS_DYING); + + if (plugin->ref_count > 0) + error= WARN_PLUGIN_BUSY; + + mysql_mutex_unlock(&LOCK_plugin); + return error; +} + +int clone_handle_drop() +{ + if (!clone_handle) + return 1; + + delete clone_handle; + + clone_handle= nullptr; + + if (opt_bootstrap) + /* Inform that database initialization in progress. */ + return ER_SERVER_SHUTDOWN; + + return 0; +} + +Clone_handler *clone_plugin_lock(THD *thd, plugin_ref *plugin) +{ + LEX_CSTRING cstr= {clone_plugin_nm, strlen(clone_plugin_nm)}; + *plugin= my_plugin_lock_by_name(thd, &cstr, + MariaDB_CLONE_PLUGIN); + mysql_mutex_lock(&LOCK_plugin); + + /* Return handler only if the plugin is ready. We might successfully + lock the plugin when initialization is progress. */ + if (*plugin && plugin_state(*plugin) == PLUGIN_IS_READY) + { + mysql_mutex_unlock(&LOCK_plugin); + assert(clone_handle); + return clone_handle; + } + mysql_mutex_unlock(&LOCK_plugin); + return nullptr; +} + +void clone_plugin_unlock(THD *thd, plugin_ref plugin) +{ + plugin_unlock(thd, plugin); +} + +std::atomic Clone_handler::s_provision_in_progress{0}; +std::atomic Clone_handler::s_is_data_dropped{false}; + +namespace clone_common +{ +bool ends_with(const char *str, const char *suffix) +{ + size_t suffix_len= strlen(suffix); + size_t str_len= strlen(str); + + return (str_len >= suffix_len && + strcmp(str + str_len - suffix_len, suffix) == 0); +} + +static void parse_db_table_from_file_path(const char *filepath, char *dbname, + char *tablename) +{ + dbname[0]= '\0'; + tablename[0]= '\0'; + const char *dbname_start= nullptr; + const char *tablename_start= filepath; + const char *const_ptr; + while ((const_ptr= strchr(tablename_start, FN_LIBCHAR)) != NULL) + { + dbname_start = tablename_start; + tablename_start = const_ptr + 1; + } + if (!dbname_start) + return; + size_t dbname_len = tablename_start - dbname_start - 1; + if (dbname_len >= FN_REFLEN) + dbname_len = FN_REFLEN-1; + + strmake(dbname, dbname_start, dbname_len); + strmake(tablename, tablename_start, FN_REFLEN-1); + char *ptr; + if ((ptr = strchr(tablename, '.'))) *ptr= '\0'; + if ((ptr = strstr(tablename, "#P#"))) *ptr= '\0'; + if ((ptr = strstr(tablename, "#i#"))) *ptr= '\0'; +} + +std::tuple +convert_filepath_to_tablename(const char *filepath) +{ + char db_name_orig[FN_REFLEN]; + char table_name_orig[FN_REFLEN]; + parse_db_table_from_file_path(filepath, db_name_orig, table_name_orig); + if (!db_name_orig[0] || !table_name_orig[0]) + return std::make_tuple("", "", ""); + char db_name_conv[FN_REFLEN]; + char table_name_conv[FN_REFLEN]; + filename_to_tablename(db_name_orig, db_name_conv, sizeof(db_name_conv)); + filename_to_tablename( + table_name_orig, table_name_conv, sizeof(table_name_conv)); + if (!db_name_conv[0] || !table_name_conv[0]) + return std::make_tuple("", "", ""); + return std::make_tuple(db_name_conv, table_name_conv, + std::string(db_name_orig).append("/").append(table_name_orig)); +} + +bool is_log_table(const char *dbname, const char *tablename) +{ + assert(dbname); + assert(tablename); + + LEX_CSTRING lex_db; + LEX_CSTRING lex_table; + + lex_db.str = dbname; + lex_db.length = strlen(dbname); + lex_table.str = tablename; + lex_table.length = strlen(tablename); + + if (!lex_string_eq(&MYSQL_SCHEMA_NAME, &lex_db)) + return false; + + if (lex_string_eq(&GENERAL_LOG_NAME, &lex_table)) + return true; + + if (lex_string_eq(&SLOW_LOG_NAME, &lex_table)) + return true; + + return false; +} + +bool is_stats_table(const char *dbname, const char *tablename) +{ + assert(dbname); + assert(tablename); + + LEX_CSTRING lex_db; + LEX_CSTRING lex_table; + lex_db.str = dbname; + lex_db.length = strlen(dbname); + lex_table.str = tablename; + lex_table.length = strlen(tablename); + + if (!lex_string_eq(&MYSQL_SCHEMA_NAME, &lex_db)) + return false; + + CHARSET_INFO *ci= system_charset_info; + + return (lex_table.length > 4 && + /* one of mysql.*_stat tables, but not mysql.innodb* tables*/ + ((my_tolower(ci, lex_table.str[lex_table.length-5]) == 's' && + my_tolower(ci, lex_table.str[lex_table.length-4]) == 't' && + my_tolower(ci, lex_table.str[lex_table.length-3]) == 'a' && + my_tolower(ci, lex_table.str[lex_table.length-2]) == 't' && + my_tolower(ci, lex_table.str[lex_table.length-1]) == 's') && + !(my_tolower(ci, lex_table.str[0]) == 'i' && + my_tolower(ci, lex_table.str[1]) == 'n' && + my_tolower(ci, lex_table.str[2]) == 'n' && + my_tolower(ci, lex_table.str[3]) == 'o'))); +} + +int foreach_file_in_dir( + const fsys::path& dir_path, + const std::function& callback, + const std::set& file_extns, + const std::set& file_types, int max_depth) +{ + try + { + if (!fsys::exists(dir_path) || !fsys::is_directory(dir_path)) + { + sql_print_error("Error: %s is not a valid directory.", dir_path.c_str()); + return -1; + } + + auto options= fsys::directory_options::skip_permission_denied; + for (auto it= fsys::recursive_directory_iterator(dir_path, options); + it != fsys::recursive_directory_iterator(); ++it) + { + auto& entry= *it; + int depth= it.depth(); + + if (max_depth >= 0 && depth > max_depth) + { + it.disable_recursion_pending(); + continue; + } + + fsys::file_type type= entry.status().type(); + fsys::path filePath= entry.path(); + + if (!file_types.empty() && file_types.find(type) == file_types.end()) + continue; + + if (!file_extns.empty()) + { + std::string extension = filePath.extension().string(); + if (file_extns.find(extension) == file_extns.end()) + continue; + } + callback(filePath); + } + } + catch (const fsys::filesystem_error& e) + { + sql_print_error("File System Error: %s", e.what()); + return -1; + } + catch (const std::exception& e) + { + sql_print_error("General Error: %s", e.what()); + return -1; + } + return 0; +} + +static std::vector read_frm_image(File file) +{ + std::vector frm_image; + MY_STAT state; + + if (mysql_file_fstat(file, &state, MYF(MY_WME))) + return frm_image; + + frm_image.resize((size_t)state.st_size, 0); + + if (mysql_file_read(file, frm_image.data(), (size_t)state.st_size, + MYF(MY_NABP))) + frm_image.clear(); + return frm_image; +} + +static +std::string get_table_version_from_image(const std::vector &frm_image) +{ + DBUG_ASSERT(frm_image.size() >= 64); + if (!strncmp((char*) frm_image.data(), "TYPE=VIEW\n", 10)) + return {}; + + if (!is_binary_frm_header(frm_image.data())) + return {}; + + /* Length of the MariaDB extra2 segment in the form file. */ + uint len= uint2korr(frm_image.data() + 4); + const uchar *extra2= frm_image.data() + 64; + + if (*extra2 == '/') // old frm had '/' there + return {}; + + const uchar *e2end= extra2 + len; + while (extra2 + 3 <= e2end) + { + uchar type= *extra2++; + size_t length= *extra2++; + if (!length) + { + if (extra2 + 2 >= e2end) + return {}; + length= uint2korr(extra2); + extra2+= 2; + + if (length < 256) + return {}; + } + if (extra2 + length > e2end) + return {}; + if (type == EXTRA2_TABLEDEF_VERSION) + { + char buff[MY_UUID_STRING_LENGTH]; + my_uuid2str(extra2, buff, 1); + return std::string(buff, buff + MY_UUID_STRING_LENGTH); + } + extra2+= length; + } + return {}; +} + +std::string read_table_version_id(File file) +{ + auto frm_image= read_frm_image(file); + if (frm_image.empty()) + return {}; + return get_table_version_from_image(frm_image); +} +} // namespace clone_common diff --git a/sql/clone_handler.h b/sql/clone_handler.h new file mode 100644 index 0000000000000..801c1be2e0f56 --- /dev/null +++ b/sql/clone_handler.h @@ -0,0 +1,170 @@ +/* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ + +/** +@file clone_handler.h +Clone handler interface to access clone plugin +*/ + +#ifndef CLONE_HANDLER_INCLUDED +#define CLONE_HANDLER_INCLUDED + +#include +#include +#include +#include +# if defined __GNUC__ && __GNUC__ > 5 && __GNUC__ < 9 && !defined(_WIN32) && !defined(__APPLE__) && !defined(_clang_) + #include + namespace fsys= std::experimental::filesystem; +#else + #include + namespace fsys= std::filesystem; +#endif +#include +#include +#include "sql_plugin.h" + +class THD; +struct Mysql_clone; + +/** + Number of PSI_statement_info instruments + for clone statements. +*/ + +#define CLONE_PSI_STATEMENT_COUNT 5 + +/** + Clone plugin handler to convenient way to. Takes +*/ +class Clone_handler { + public: + /** Constructor: Initialize plugin name */ + Clone_handler(const char *plugin_name_arg) : m_plugin_handle(nullptr) { + m_plugin_name.assign(plugin_name_arg); + } + + /** Initialize plugin handle + @return error code */ + int init(); + + /** Clone handler interface for local clone. + @param[in] thd server thread handle + @param[in] data_dir cloned data directory + @return error code */ + int clone_local(THD *thd, const char *data_dir); + + /** Clone handler interface for remote clone client. + @param[in] thd server thread handle + @param[in] remote_host remote host IP address + @param[in] remote_port remote server port + @param[in] remote_user remote user name + @param[in] remote_passwd remote user's password + @param[in] data_dir cloned data directory + @param[in] ssl_mode remote connection ssl mode + @return error code */ + int clone_remote_client(THD *thd, const char *remote_host, uint remote_port, + const char *remote_user, const char *remote_passwd, + const char *data_dir, int ssl_mode); + + /** Clone handler interface for remote clone server. + @param[in] thd server thread handle + @param[in] socket network socket to remote client + @return error code */ + int clone_remote_server(THD *thd, MYSQL_SOCKET socket); + + /** Get donor error and message for ER_CLONE_DONOR error. + @param[out] error donor error number + @param[out] message error message + @return true, iff successful. */ + static bool get_donor_error(int &error, const char *&message); + + /** @return false only if no user data is dropped yet. */ + static bool is_data_dropped() { return (s_is_data_dropped); } + + /** Must set before dropping any user data. */ + static void set_drop_data() { s_is_data_dropped.store(true); } + + /** @return true, if clone provisioning in progress. */ + static bool is_provisioning() { return (s_provision_in_progress > 0); } + + private: + /** Validate clone data directory and convert to os format + @param[in] in_dir user specified clone directory + @param[out] out_dir data directory in native os format + @return error code */ + int validate_dir(const char *in_dir, char *out_dir); + + private: + /** True if clone provisioning in progress. */ + static std::atomic s_provision_in_progress; + + /** True, if any user data is dropped by clone. */ + static std::atomic s_is_data_dropped; + + /** Clone plugin name */ + std::string m_plugin_name; + + /** Clone plugin handle */ + Mysql_clone *m_plugin_handle; +}; + +/** Check if the clone plugin is installed and lock. If the plugin is ready, +return the handler to caller. +@param[in] thd server thread handle +@param[out] plugin plugin reference +@return clone handler on success otherwise NULL */ +Clone_handler *clone_plugin_lock(THD *thd, plugin_ref *plugin); + +/** Unlock the clone plugin. +@param[in] thd server thread handle +@param[out] plugin plugin reference */ +void clone_plugin_unlock(THD *thd, plugin_ref plugin); + +namespace clone_common +{ +/** Check if string ends with given suffix. +@return true if string ends with given suffix. */ +bool ends_with(const char *str, const char *suffix); + +std::tuple +convert_filepath_to_tablename(const char *filepath); + +bool is_log_table(const char *dbname, const char *tablename); + +bool is_stats_table(const char *dbname, const char *tablename); + +void foreach_file_in_db_dirs(const char *dir_path, + std::function func); + +int foreach_file_in_dir( + const fsys::path& dir_path, + const std::function& callback, + const std::set& file_extns= {}, + const std::set& file_types= {fsys::file_type::regular}, + int max_depth= 1); + +std::string read_table_version_id(File file); +} // namespace clone_common + +#endif /* CLONE_HANDLER_INCLUDED */ diff --git a/sql/handler.h b/sql/handler.h index 4557e2adf103a..08fe74670420a 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -21,6 +21,7 @@ /* Definitions for parameters to do with handler-routines */ +#include #include "sql_const.h" #include "sql_basic_types.h" #include "mysqld.h" /* server_id */ @@ -1252,6 +1253,389 @@ typedef class st_select_lex SELECT_LEX; typedef class st_select_lex_unit SELECT_LEX_UNIT; typedef struct st_order ORDER; +/** Clone start operation mode */ +enum Ha_clone_mode { + /** Start a new clone operation */ + HA_CLONE_MODE_START, + + /** Re-start a clone operation after failure */ + HA_CLONE_MODE_RESTART, + + /** Add a new task to a running clone operation */ + HA_CLONE_MODE_ADD_TASK, + + /** Get version for transfer data format */ + HA_CLONE_MODE_VERSION, + + /** Max value for clone mode */ + HA_CLONE_MODE_MAX +}; + +enum Ha_clone_stage { + /* Concurrent clone with DDL and DML. */ + HA_CLONE_STAGE_CONCURRENT =0, + + /* New Non-Transactional DMLs blocked. */ + HA_CLONE_STAGE_NT_DML_BLOCKED, + + /* All DDL blocked. For Server owned metadata files (FRM) + and SE's needing DDL to be blocked. */ + HA_CLONE_STAGE_DDL_BLOCKED, + + /* Commit blocked. For consistent SE snapshot with binary log. */ + HA_CLONE_STAGE_SNAPSHOT, + + /* Clone any archived data at the end. Doesn't need to block anything. */ + HA_CLONE_STAGE_END, + + /* Maximum value used fot array bound only. */ + HA_CLONE_STAGE_MAX +}; + +/** Clone operation types. */ +enum Ha_clone_type : size_t { + /** Caller must block all write operation to the SE. */ + HA_CLONE_BLOCKING, + + /** For transactional SE, archive redo to support concurrent dml */ + HA_CLONE_REDO, + + /** For transactional SE, track page changes to support concurrent dml */ + HA_CLONE_PAGE, + + /** For transactional SE, use both page tracking and redo to optimize + clone with concurrent dml. Currently supported by Innodb. */ + HA_CLONE_HYBRID, + + /** SE supports multiple threads for clone */ + HA_CLONE_MULTI_TASK, + + /** SE supports restarting clone after network failure */ + HA_CLONE_RESTART, + + /** Maximum value of clone type */ + HA_CLONE_TYPE_MAX +}; + +using Ha_clone_flagset = std::bitset; + +/** File reference for clone */ +struct Ha_clone_file { + /** File reference type */ + enum { + /** File handle */ + FILE_HANDLE, + + /** File descriptor */ + FILE_DESC + + } type; + + /** File reference */ + union { + /** File descriptor */ + int file_desc; + + /** File handle for windows */ + void *file_handle; + }; +}; + +/* Abstract callback interface to stream data back to the caller. */ +class Ha_clone_cbk { + protected: + /** Constructor to initialize members. */ + Ha_clone_cbk() + : m_hton(), + m_loc_idx(), + m_client_buff_size(), + m_data_desc(), + m_desc_len(), + m_src_name(), + m_dest_name(), + m_state_estimate(), + m_flag() {} + + public: + /** Callback providing data from current position of a + file descriptor of specific length. + @param[in] from_file source file to read from + @param[in] len data length + @return error code */ + virtual int file_cbk(Ha_clone_file from_file, uint len) = 0; + + /** Callback providing data in buffer of specific length. + @param[in] from_buffer source buffer to read from + @param[in] len data length + @return error code */ + virtual int buffer_cbk(uchar *from_buffer, uint len) = 0; + + /** Callback providing a file descriptor to write data starting + from current position. + @param[in] to_file destination file to write data + @return error code */ + virtual int apply_file_cbk(Ha_clone_file to_file) = 0; + + /** Callback to get data in buffer. + @param[out] to_buffer data buffer + @param[out] len data length + @return error code */ + virtual int apply_buffer_cbk(uchar *&to_buffer, uint &len) = 0; + + /** virtual destructor. */ + virtual ~Ha_clone_cbk() = default; + + /** Set current storage engine handlerton. + @param[in] hton SE handlerton */ + void set_hton(handlerton *hton) { m_hton = hton; } + + /** Get current storage engine handlerton. + @return SE handlerton */ + handlerton *get_hton() { return (m_hton); } + + /** Set caller's transfer buffer size. SE can adjust the data chunk size + based on this parameter. + @param[in] size buffer size in bytes */ + void set_client_buffer_size(uint size) { m_client_buff_size = size; } + + /** Get caller's transfer buffer size. + @return buffer size in bytes */ + uint get_client_buffer_size() { return (m_client_buff_size); } + + /** Set current SE index. + @param[in] idx SE index in locator array */ + void set_loc_index(uint idx) { m_loc_idx = idx; } + + /** Get current SE index. + @return SE index in locator array */ + uint get_loc_index() { return (m_loc_idx); } + + /** Set data descriptor. SE specific descriptor for the + data transferred by the callbacks. + @param[in] desc serialized data descriptor + @param[in] len length of the descriptor byte stream */ + void set_data_desc(const uchar *desc, uint len) { + m_data_desc = desc; + m_desc_len = len; + } + + /** Get data descriptor. SE specific descriptor for the + data transferred by the callbacks. + @param[out] lenp length of the descriptor byte stream + @return pointer to the serialized data descriptor */ + const uchar *get_data_desc(uint *lenp) { + if (lenp != nullptr) { + *lenp = m_desc_len; + } + + return (m_data_desc); + } + + /** Get SE source file name. Used for debug printing and error message. + @return null terminated string for source file name */ + const char *get_source_name() { return (m_src_name); } + + /** Set SE source file name. + @param[in] name null terminated string for source file name */ + void set_source_name(const char *name) { m_src_name = name; } + + /** Get SE destination file name. Used for debug printing and error message. + @return null terminated string for destination file name */ + const char *get_dest_name() { return (m_dest_name); } + + /** Set SE destination file name. + @param[in] name null terminated string for destination file name */ + void set_dest_name(const char *name) { m_dest_name = name; } + + /** Clear all flags set by SE */ + void clear_flags() { m_flag = 0; } + + /** Mark that ACK is needed for the data transfer before returning + from callback. Set by SE. */ + void set_ack() { m_flag |= HA_CLONE_ACK; } + + /** Check if ACK is needed for the data transfer + @return true if ACK is needed */ + bool is_ack_needed() const { return (m_flag & HA_CLONE_ACK); } + + /** Mark that the file descriptor is opened for read/write + with OS buffer cache. For O_DIRECT, the flag is not set. */ + void set_os_buffer_cache() { m_flag |= HA_CLONE_FILE_CACHE; } + + /** Check if the file descriptor is opened for read/write with OS + buffer cache. Currently clone avoids using zero copy (sendfile on linux), + if SE is using O_DIRECT. This improves data copy performance. + @return true if O_DIRECT is not used */ + bool is_os_buffer_cache() const { return (m_flag & HA_CLONE_FILE_CACHE); } + + /** Mark that the file can be transferred with zero copy. */ + void set_zero_copy() { m_flag |= HA_CLONE_ZERO_COPY; } + + /** Check if zero copy optimization is suggested. */ + bool is_zero_copy() const { return (m_flag & HA_CLONE_ZERO_COPY); } + + /** Mark that data needs secure transfer. */ + void set_secure() { m_flag |= HA_CLONE_SECURE; } + + /** Check if data needs secure transfer. */ + bool is_secure() const { return (m_flag & HA_CLONE_SECURE); } + + /** Set state information and notify state change. + @param[in] estimate estimated bytes for current state. */ + void mark_state_change(uint64_t estimate) { + m_flag |= HA_CLONE_STATE_CHANGE; + m_state_estimate = estimate; + } + + /** Check if SE notified state change. */ + bool is_state_change(uint64_t &estimate) { + estimate = m_state_estimate; + return (m_flag & HA_CLONE_STATE_CHANGE); + } + + private: + /** Handlerton for the SE */ + handlerton *m_hton; + + /** SE index in caller's locator array */ + uint m_loc_idx; + + /** Caller's transfer buffer size. */ + uint m_client_buff_size; + + /** SE's Serialized data descriptor */ + const uchar *m_data_desc; + + /** SE's Serialized descriptor length. */ + uint m_desc_len; + + /** Current source file name */ + const char *m_src_name; + + /** Current destination file name */ + const char *m_dest_name; + + /** Estimated bytes to be transferred. */ + uint64_t m_state_estimate; + + /** Flag storing data related options */ + int m_flag; + + /** Acknowledgement is needed for the data transfer. */ + const int HA_CLONE_ACK = 0x01; + + /** Data file is opened for read/write with OS buffer cache. */ + const int HA_CLONE_FILE_CACHE = 0x02; + + /** Data file can be transferred with zero copy. */ + const int HA_CLONE_ZERO_COPY = 0x04; + + /** Data needs to be transferred securely over SSL connection. */ + const int HA_CLONE_SECURE = 0x08; + + /** State change notification by SE. */ + const int HA_CLONE_STATE_CHANGE = 0x10; +}; + + +/** Get capability flags for clone operation +@param[out] flags capability flag */ +using clone_capability_t = void (*)(Ha_clone_flagset &flags); + +/** Begin copy from source database +@param[in] thd server thread handle +@param[in,out] loc locator +@param[in,out] loc_len locator length +@param[out] task_id task identifier +@param[in] type clone type +@param[in] mode mode for starting clone +@return error code */ +using clone_begin_t = int (*)(THD *thd, const uchar *&loc, uint &loc_len, + uint &task_id, Ha_clone_type type, + Ha_clone_mode mode); + +/** Copy data from source database in chunks via callback +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] stage Clone execution Stage +@param[in] cbk callback interface for sending data +@return error code */ +using clone_copy_t = int (*)(THD *thd, const uchar *loc, uint loc_len, + uint task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk); + +/** Acknowledge data transfer to source database +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err inform any error occurred +@param[in] cbk callback interface +@return error code */ +using clone_ack_t = int (*)(THD *thd, const uchar *loc, uint loc_len, + uint task_id, int in_err, Ha_clone_cbk *cbk); + +/** End copy from source database +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err error code when ending after error +@return error code */ +using clone_end_t = int (*)(THD *thd, const uchar *loc, uint loc_len, + uint task_id, int in_err); + +/** Begin apply to destination database +@param[in] thd server thread handle +@param[in,out] loc locator +@param[in,out] loc_len locator length +@param[in] task_id task identifier +@param[in] mode mode for starting clone +@param[in] data_dir target data directory +@return error code */ +using clone_apply_begin_t = int (*)(THD *thd, const uchar *&loc, uint &loc_len, + uint &task_id, Ha_clone_mode mode, + const char *data_dir); + +/** Apply data to destination database in chunks via callback +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err inform any error occurred +@param[in] cbk callback interface for receiving data +@return error code */ +using clone_apply_t = int (*)(THD *thd, const uchar *loc, uint loc_len, + uint task_id, int in_err, Ha_clone_cbk *cbk); + +/** End apply to destination database +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err error code when ending after error +@return error code */ +using clone_apply_end_t = int (*)(THD *thd, const uchar *loc, uint loc_len, + uint task_id, int in_err); + +struct clone_interface_t { + /* Get clone capabilities of an SE */ + clone_capability_t clone_capability; + + /* Interfaces to copy data. */ + clone_begin_t clone_begin; + clone_copy_t clone_copy; + clone_ack_t clone_ack; + clone_end_t clone_end; + + /* Interfaces to apply data. */ + clone_apply_begin_t clone_apply_begin; + clone_apply_t clone_apply; + clone_apply_end_t clone_apply_end; +}; + struct transaction_participant { /* @@ -1746,6 +2130,9 @@ struct handlerton : public transaction_participant my_bool signal) __attribute__((nonnull)); int (*set_checkpoint)(handlerton *hton, const XID *xid); int (*get_checkpoint)(handlerton *hton, XID* xid); + + /** Clone data transfer interfaces */ + clone_interface_t clone_interface; }; diff --git a/sql/lex.h b/sql/lex.h index 41a34ef738989..358a4885f025f 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -119,6 +119,7 @@ SYMBOL symbols[] = { { "CLASS_ORIGIN", SYM(CLASS_ORIGIN_SYM)}, { "CLIENT", SYM(CLIENT_SYM)}, { "CLOB", SYM(CLOB_MARIADB_SYM)}, + { "CLONE", SYM(CLONE_SYM)}, { "CLOSE", SYM(CLOSE_SYM)}, { "COALESCE", SYM(COALESCE)}, { "CODE", SYM(CODE_SYM)}, @@ -305,6 +306,7 @@ SYMBOL symbols[] = { { "INSERT", SYM(INSERT)}, { "INSERT_METHOD", SYM(INSERT_METHOD)}, { "INSTALL", SYM(INSTALL_SYM)}, + { "INSTANCE", SYM(INSTANCE_SYM)}, { "INT", SYM(INT_SYM)}, { "INT1", SYM(TINYINT)}, { "INT2", SYM(SMALLINT)}, diff --git a/sql/mysqld.cc b/sql/mysqld.cc index a3810f640c50b..ec7ce56fee2ee 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -3477,6 +3477,7 @@ SHOW_VAR com_status_vars[]= { {"change_db", STMT_STATUS(SQLCOM_CHANGE_DB)}, {"change_master", STMT_STATUS(SQLCOM_CHANGE_MASTER)}, {"check", STMT_STATUS(SQLCOM_CHECK)}, + {"clone", STMT_STATUS(SQLCOM_CLONE)}, {"checksum", STMT_STATUS(SQLCOM_CHECKSUM)}, {"commit", STMT_STATUS(SQLCOM_COMMIT)}, {"compound_sql", STMT_STATUS(SQLCOM_COMPOUND)}, @@ -3678,6 +3679,8 @@ void init_sql_statement_info() } var++; } + /* "statement/sql/clone" will mutate to clone plugin statement */ + sql_statement_info[(uint)SQLCOM_CLONE].m_flags = PSI_FLAG_MUTABLE; DBUG_ASSERT(strcmp(sql_statement_info[(uint) SQLCOM_SELECT].m_name, "select") == 0); DBUG_ASSERT(strcmp(sql_statement_info[(uint) SQLCOM_SIGNAL].m_name, "signal") == 0); @@ -3697,6 +3700,8 @@ void init_com_statement_info() /* "statement/abstract/query" can mutate into "statement/sql/..." */ com_statement_info[(uint) COM_QUERY].m_flags= PSI_FLAG_MUTABLE; + /* "statement/com/clone" will mutate to clone plugin statement */ + com_statement_info[(uint)COM_CLONE].m_flags = PSI_FLAG_MUTABLE; } #endif @@ -9995,9 +10000,18 @@ void init_server_psi_keys(void) #ifdef HAVE_PSI_STATEMENT_INTERFACE init_sql_statement_info(); - count= array_elements(sql_statement_info); + + /* Register [0 .. SQLCOM_CLONE - 1] as "statement/sql/..." */ + count = (int)SQLCOM_CLONE; mysql_statement_register(category, sql_statement_info, count); + /* Exclude SQLCOM_CLONE as it mutates and is registered as abstract. */ + count = (int)SQLCOM_END - (int)SQLCOM_CLONE; + mysql_statement_register(category, &sql_statement_info[(int)SQLCOM_CLONE + 1], + count); + category = "abstract"; + mysql_statement_register(category, &sql_statement_info[(int)SQLCOM_CLONE], 1); + init_sp_psi_keys(); category= "com"; @@ -10009,17 +10023,22 @@ void init_server_psi_keys(void) count= (int) COM_QUERY; mysql_statement_register(category, com_statement_info, count); + /* Exclude COM_CLONE as it would mutate */ + count = (int)COM_CLONE - (int)COM_QUERY - 1; + mysql_statement_register(category, &com_statement_info[(int)COM_QUERY + 1], + count); /* - Register [COM_QUERY + 1 .. COM_END] as "statement/com/..." + Register [COM_CLONE + 1 .. COM_END] as "statement/com/..." */ - count= (int) COM_END - (int) COM_QUERY; - mysql_statement_register(category, & com_statement_info[(int) COM_QUERY + 1], count); - + count= (int) COM_END - (int) COM_CLONE; + mysql_statement_register(category, & com_statement_info[(int) COM_CLONE + 1], + count); category= "abstract"; /* Register [COM_QUERY] as "statement/abstract/com_query" */ mysql_statement_register(category, & com_statement_info[(int) COM_QUERY], 1); + mysql_statement_register(category, & com_statement_info[(int) COM_CLONE], 1); /* When a new packet is received, @@ -10170,3 +10189,7 @@ static int calculate_server_uid(char *dest) return 0; } + +extern "C" PSI_file_key get_key_file_frm() { + return key_file_frm; +} diff --git a/sql/service_clone_protocol.cc b/sql/service_clone_protocol.cc new file mode 100644 index 0000000000000..4cf455179245c --- /dev/null +++ b/sql/service_clone_protocol.cc @@ -0,0 +1,821 @@ +/* Copyright (c) 2018, 2024, Oracle and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is designed to work with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have either included with + the program or referenced in the documentation. + + 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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include "sql_plugin.h" +#include "my_global.h" +#include "mysql/plugin.h" +#include "mysql/service_clone_protocol.h" + +#include "my_byteorder.h" +#include "mysql.h" +#include "mysqld.h" +#include "protocol.h" +#include "set_var.h" +#include "sql_class.h" +#include "sql_show.h" +// #include "ssl_init_callback.h" +#include "sys_vars_shared.h" +#include "sql_common.h" +#include "backup.h" +#include "mdl.h" +#include + +/** The minimum idle timeout in seconds. It is kept at 8 hours which is also +the Server default. Currently recipient sends ACK during state transition. +In future we could have better time controlled ACK. */ +static const uint32_t MIN_IDLE_TIME_OUT_SEC = 8 * 60 * 60; + +/** Minimum read timeout in seconds. Maintain above the donor ACK frequency. */ +static const uint32_t MIN_READ_TIME_OUT_SEC = 30; + +/** Minimum write timeout in seconds. Disallow configuring it to too low. We +might need a separate clone configuration in future or retry on failure. */ +static const uint32_t MIN_WRITE_TIME_OUT_SEC = 60; + +/** Set Network read timeout. +@param[in,out] net network object +@param[in] timeout time out in seconds */ +static void set_read_timeout(NET *net, uint32_t timeout) +{ + if (timeout < MIN_READ_TIME_OUT_SEC) { + timeout = MIN_READ_TIME_OUT_SEC; + } + my_net_set_read_timeout(net, timeout); +} + +/** Set Network write timeout. +@param[in,out] net network object +@param[in] timeout time out in seconds */ +static void set_write_timeout(NET *net, uint32_t timeout) +{ + if (timeout < MIN_WRITE_TIME_OUT_SEC) { + timeout = MIN_WRITE_TIME_OUT_SEC; + } + my_net_set_write_timeout(net, timeout); +} + +/** Set Network idle timeout. +@param[in,out] net network object +@param[in] timeout time out in seconds */ +static void set_idle_timeout(NET *net, uint32_t timeout) +{ + if (timeout < MIN_IDLE_TIME_OUT_SEC) { + timeout = MIN_IDLE_TIME_OUT_SEC; + } + my_net_set_read_timeout(net, timeout); +} + +MYSQL_THD create_thd(); +void destroy_thd(MYSQL_THD thd); + +THD* clone_start_statement(THD *thd, PSI_thread_key thread_key, + PSI_statement_key statement_key, + const char *thd_name) +{ + if (!thd) { + my_thread_init(); + /* Create thread with input key for PFS */ + thd= create_thd(); +#ifdef HAVE_PSI_THREAD_INTERFACE + PSI_thread *psi= PSI_CALL_new_thread(thread_key, NULL, 0); + PSI_CALL_set_thread_os_id(psi); + PSI_CALL_set_thread(psi); +#endif + my_thread_set_name(thd_name); + } + + /* Create and set PFS statement key */ + if (statement_key != PSI_NOT_INSTRUMENTED) { + if (thd->m_statement_psi == nullptr) { + thd->m_statement_psi = MYSQL_START_STATEMENT( + &thd->m_statement_state, statement_key, thd->get_db(), + thd->db.length, thd->charset(), nullptr); + } else if (thd->get_command() != COM_STMT_EXECUTE) { + thd->m_statement_psi= + MYSQL_REFINE_STATEMENT(thd->m_statement_psi, statement_key); + } + } + return thd; +} + +void clone_finish_statement(THD *thd) +{ + assert(thd->m_statement_psi == nullptr); + thd->set_psi(nullptr); + destroy_thd(thd); + my_thread_end(); +} + +// extern "C" +MYSQL* clone_connect(THD * thd, const char *host, uint32_t port, + const char *user, const char *passwd, + mysql_clone_ssl_context *ssl_ctx, MYSQL_SOCKET *socket) +{ + /* Set default */ + uint net_read_timeout = MIN_READ_TIME_OUT_SEC; + uint net_write_timeout = MIN_WRITE_TIME_OUT_SEC; + + /* Clean any previous Error and Warnings in THD */ + if (thd != nullptr) { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + + net_read_timeout = thd->variables.net_read_timeout; + net_write_timeout = thd->variables.net_write_timeout; + } + + MYSQL *mysql; + MYSQL *ret_mysql; + + /* Connect using classic protocol */ + mysql = mysql_init(nullptr); + + // auto client_ssl_mode = static_cast(ssl_ctx->m_ssl_mode); + + /* Get server public key for RSA key pair-based password exchange.*/ + // bool get_key = true; + // mysql_options(mysql, MYSQL_OPT_GET_SERVER_PUBLIC_KEY, &get_key); + + if (ssl_ctx->m_ssl_mode > 0) + { + mysql->options.use_ssl= 1; + /* Verify server's certificate */ + // if (ssl_ctx->m_ssl_ca != nullptr) { + // client_ssl_mode = SSL_MODE_VERIFY_CA; + // } + + mysql_options(mysql, MYSQL_OPT_SSL_KEY, ssl_ctx->m_ssl_key); + mysql_options(mysql, MYSQL_OPT_SSL_CERT, ssl_ctx->m_ssl_cert); + mysql_options(mysql, MYSQL_OPT_SSL_CA, ssl_ctx->m_ssl_ca); + + mysql_options(mysql, MYSQL_OPT_SSL_CAPATH, opt_ssl_capath); + mysql_options(mysql, MYSQL_OPT_SSL_CIPHER, opt_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + // mysql_options(mysql, MYSQL_OPT_TLS_VERSION, tls_version); + // mysql_options(mysql, MYSQL_OPT_TLS_CIPHERSUITES, ciphersuites.c_str()); + } + else + { + // mysql_options(mysql, MYSQL_OPT_SSL_MODE, &client_ssl_mode); + mysql->options.use_ssl= 0; + } + + auto timeout = static_cast(connect_timeout); + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, + reinterpret_cast(&timeout)); + + /* Enable compression. */ + if (ssl_ctx->m_enable_compression) + mysql_options(mysql, MYSQL_OPT_COMPRESS, nullptr); + // mysql_extension_set_server_extn(mysql, ssl_ctx->m_server_extn); + + ret_mysql= + mysql_real_connect(mysql, host, user, passwd, nullptr, port, nullptr, 0); + + if (ret_mysql == nullptr) { + char err_buf[MYSYS_ERRMSG_SIZE + 64]; + snprintf(err_buf, sizeof(err_buf), "Connect failed: %u : %s", + mysql_errno(mysql), mysql_error(mysql)); + + my_error(ER_CLONE_DONOR, MYF(0), err_buf); + const char* format= my_get_err_msg(ER_CLONE_CLIENT_TRACE); + my_printf_error(ER_CLONE_CLIENT_TRACE, format, + ME_ERROR_LOG_ONLY|ME_NOTE, err_buf); + mysql_close(mysql); + return nullptr; + } + + NET *net= &mysql->net; + Vio *vio= net->vio; + + *socket= vio->mysql_socket; + + net_clear_error(net); + net_clear(net, true); + + /* Set network read/write timeout */ + set_read_timeout(net, net_read_timeout); + set_write_timeout(net, net_write_timeout); + + if (thd != nullptr) { + /* Set current active vio so that shutdown and KILL + signals can wake up current thread. */ + thd->set_clone_vio(net->vio); + } + + /* Load clone plugin in remote */ + auto result= simple_command(mysql, COM_CLONE, nullptr, 0, 0); + + if (result) { + if (thd != nullptr) { + thd->clear_clone_vio(); + } + char err_buf[MYSYS_ERRMSG_SIZE + 64]; + snprintf(err_buf, sizeof(err_buf), "%d : %s", net->last_errno, + net->last_error); + + my_error(ER_CLONE_DONOR, MYF(0), err_buf); + + snprintf(err_buf, sizeof(err_buf), "COM_CLONE failed: %d : %s", + net->last_errno, net->last_error); + const char* format= my_get_err_msg(ER_CLONE_CLIENT_TRACE); + my_printf_error(ER_CLONE_CLIENT_TRACE, format, + ME_ERROR_LOG_ONLY|ME_NOTE, err_buf); + mysql_close(mysql); + mysql= nullptr; + } + return mysql; +} + +int clone_send_command(THD *thd, MYSQL *connection, bool set_active, + uchar command, uchar *com_buffer, size_t buffer_length) +{ + NET *net = &connection->net; + + if (net->last_errno != 0) { + return static_cast(net->last_errno); + } + + net_clear_error(net); + net_clear(net, true); + + if (set_active && thd->killed != NOT_KILLED) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + + auto result = + net_write_command(net, command, nullptr, 0, com_buffer, buffer_length); + if (!result) { + return 0; + } + + int err = static_cast(net->last_errno); + + /* Check if query is interrupted */ + if (set_active && thd->killed != NOT_KILLED) { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err = ER_QUERY_INTERRUPTED; + } + + assert(err != 0); + return err; +} + +int clone_get_response(THD *thd, MYSQL *connection, bool set_active, + uint32_t timeout, uchar **packet, size_t *length, + size_t *net_length) +{ + NET *net = &connection->net; + + if (net->last_errno != 0) { + return static_cast(net->last_errno); + } + + if (set_active && thd->killed != NOT_KILLED) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + + net_new_transaction(net); + + /* Adjust read timeout if specified. */ + if (timeout != 0) { + set_read_timeout(net, timeout); + } + + /* Dummy function callback invoked before getting header. */ + auto func_before = [](NET *, void *, size_t) {}; + + /* Callback function called after receiving header. */ + auto func_after = [](NET *net_arg, void *ctx, size_t, my_bool) { + auto net_bytes = static_cast(ctx); + *net_bytes += + static_cast(uint3korr(net_arg->buff + net_arg->where_b)); + }; + + /* Use server extension callback to capture network byte information. */ + NET_SERVER server_extn; + server_extn.m_user_data = static_cast(net_length); + server_extn.m_before_header = func_before; + server_extn.m_after_header = func_after; + auto saved_extn = net->extension; + // TODO: Allow network compression + // if (saved_extn != nullptr && net->compress) + // server_extn.compress_ctx = + // (static_cast(saved_extn))->compress_ctx; + // else + // server_extn.compress_ctx.algorithm = MYSQL_UNCOMPRESSED; + net->extension = &server_extn; + + *net_length = 0; + *length = my_net_read(net); + + net->extension = saved_extn; + // server_extn.compress_ctx.algorithm = MYSQL_UNCOMPRESSED; + + /* Reset timeout back to default value. */ + set_read_timeout(net, thd->variables.net_read_timeout); + + *packet = net->read_pos; + + if (*length != packet_error && *length != 0) { + return 0; + } + + int err = static_cast(net->last_errno); + /* Check if query is interrupted */ + if (set_active && thd->killed != NOT_KILLED) { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err = ER_QUERY_INTERRUPTED; + } + + /* This error is not relevant for client but is raised by network + net_read_raw_loop() as the code is compiled in server MYSQL_SERVER. + For clone client we need to set valid client network error. */ + // if (err == ER_CLIENT_INTERACTION_TIMEOUT) { + /* purecov: begin inspected */ + // thd->clear_error(); + // thd->get_stmt_da()->reset_diagnostics_area(); + // net->last_errno = ER_NET_READ_ERROR; + // err = ER_NET_READ_ERROR; + // my_error(ER_NET_READ_ERROR, MYF(0)); + /* purecov: end */ + // } + + if (err == 0) { + net->last_errno = ER_NET_PACKETS_OUT_OF_ORDER; + err = ER_NET_PACKETS_OUT_OF_ORDER; + my_error(err, MYF(0)); + } + return err; +} + +int clone_kill(MYSQL *connection, MYSQL *kill_connection) +{ + auto kill_conn_id = kill_connection->thread_id; + + char kill_buffer[64]; + snprintf(kill_buffer, 64, "KILL CONNECTION %lu", kill_conn_id); + + auto err = mysql_real_query(connection, kill_buffer, + static_cast(strlen(kill_buffer))); + + return err; +} + +void clone_disconnect(THD *thd, MYSQL *mysql, bool is_fatal, bool clear_error) +{ + /* Make sure that the other end has switched back from clone protocol. */ + if (!is_fatal) { + is_fatal = simple_command(mysql, COM_RESET_CONNECTION, nullptr, 0, 0); + } + + if (is_fatal) { + end_server(mysql); + } + + /* Disconnect */ + mysql_close(mysql); + + /* There could be some n/w error during disconnect and we need to clear + them if requested. */ + if (thd != nullptr) { + thd->clear_clone_vio(); + + /* clear any session error, if requested */ + if (clear_error) { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + } + } +} + +void clone_get_error(THD * thd, uint32_t *err_num, const char **err_mesg) +{ + *err_num = 0; + *err_mesg = nullptr; + /* Check if THD exists. */ + if (thd == nullptr) { + return; + } + /* Check if DA exists. */ + auto da = thd->get_stmt_da(); + if (da == nullptr || !da->is_error()) { + return; + } + /* Get error from DA. */ + *err_num = da->sql_errno(); + *err_mesg = da->message(); +} + +int clone_get_command(THD *thd, uchar *command, uchar **com_buffer, + size_t *buffer_length) +{ + NET *net = &thd->net; + + if (net->last_errno != 0) { + return static_cast(net->last_errno); + } + + /* flush any data in write buffer */ + if (!net_flush(net)) { + net_new_transaction(net); + + /* Set idle timeout while waiting for commands. Earlier we used server + configuration "wait_timeout" but this causes unwanted timeout in clone + when user configures the value too low. */ + set_idle_timeout(net, thd->variables.net_wait_timeout); + + *buffer_length = my_net_read(net); + + set_read_timeout(net, thd->variables.net_read_timeout); + set_write_timeout(net, thd->variables.net_write_timeout); + + if (*buffer_length != packet_error && *buffer_length != 0) { + *com_buffer = net->read_pos; + *command = **com_buffer; + + ++(*com_buffer); + --(*buffer_length); + + return 0; + } + } + + int err = static_cast(net->last_errno); + + if (err == 0) { + net->last_errno = ER_NET_PACKETS_OUT_OF_ORDER; + err = ER_NET_PACKETS_OUT_OF_ORDER; + my_error(err, MYF(0)); + } + return err; +} + +int clone_send_response(THD * thd, bool secure, uchar *packet, size_t length) +{ + NET *net = &thd->net; + + if (net->last_errno != 0) { + return static_cast(net->last_errno); + } + + auto conn_type= vio_type(net->vio); + + if (secure && conn_type != VIO_TYPE_SSL) { + my_error(ER_CLONE_ENCRYPTION, MYF(0)); + return ER_CLONE_ENCRYPTION; + } + + net_clear(net, true); + + if (!my_net_write(net, packet, length) && !net_flush(net)) { + return 0; + } + + const int err = static_cast(net->last_errno); + + assert(err != 0); + return err; +} + +// extern "C" +int clone_send_error(THD * thd, uchar err_cmd, bool is_fatal) +{ + NET *net = &thd->net; + auto da = thd->get_stmt_da(); + + /* Consider any previous network error as fatal. */ + if (!is_fatal && net->last_errno != 0) { + is_fatal = true; + } + + if (is_fatal) { + int err = 0; + + /* Handle the case if network layer hasn't set the error in THD. */ + if (da->is_error()) { + err = da->sql_errno(); + } else { + err = ER_NET_ERROR_ON_WRITE; + my_error(err, MYF(0)); + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + vio_shutdown(thd->active_vio, SHUT_RDWR); + mysql_mutex_unlock(&thd->LOCK_thd_data); + + return err; + } + + uchar err_packet[1 + 4 + MYSQL_ERRMSG_SIZE + 1]; + uchar *buf_ptr = &err_packet[0]; + size_t packet_length = 0; + + *buf_ptr = err_cmd; + ++buf_ptr; + ++packet_length; + + char *bufp; + + if (da->is_error()) { + int4store(buf_ptr, da->sql_errno()); + buf_ptr += 4; + packet_length += 4; + + bufp = reinterpret_cast(buf_ptr); + packet_length += + snprintf(bufp, MYSQL_ERRMSG_SIZE, "%s", da->message()); + } else { + int4store(buf_ptr, ER_INTERNAL_ERROR); + buf_ptr += 4; + packet_length += 4; + + bufp = reinterpret_cast(buf_ptr); + packet_length += snprintf(bufp, MYSQL_ERRMSG_SIZE, "%s", "Unknown Error"); + } + + /* Clean error in THD */ + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + net_clear(net, true); + + if (my_net_write(net, &err_packet[0], packet_length) || net_flush(net)) { + int err = static_cast(net->last_errno); + da = thd->get_stmt_da(); + + if (err == 0 || !da->is_error()) { + net->last_errno = ER_NET_PACKETS_OUT_OF_ORDER; + err = ER_NET_PACKETS_OUT_OF_ORDER; + my_error(err, MYF(0)); + } + + mysql_mutex_lock(&thd->LOCK_thd_data); + vio_shutdown(thd->active_vio, SHUT_RDWR); + mysql_mutex_unlock(&thd->LOCK_thd_data); + + return err; + } + return 0; +} + +/** + Get configuration parameter value in utf8 + @param[in] thd server session THD + @param[in] config_name parameter name + @param[out] utf8_val parameter value in utf8 string + @return error code. +*/ +static int get_utf8_config(THD *thd, std::string config_name, + String &utf8_val) +{ + char val_buf[1024]; + SHOW_VAR show; + show.type= SHOW_SYS; + + /* Get system configuration parameter. */ + mysql_prlock_rdlock(&LOCK_system_variables_hash); + auto var= intern_find_sys_var(config_name.c_str(), config_name.length()); + mysql_prlock_unlock(&LOCK_system_variables_hash); + + if (var == nullptr) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "Clone failed to get system configuration parameter."); + return ER_INTERNAL_ERROR; + } + + show.value= reinterpret_cast(var); + show.name= var->name.str; + + mysql_mutex_lock(&LOCK_global_system_variables); + size_t val_length; + const CHARSET_INFO *fromcs; + + auto value= get_one_variable(thd, &show, OPT_GLOBAL, SHOW_SYS, nullptr, + &fromcs, val_buf, &val_length); + + uint dummy_err; + const CHARSET_INFO *tocs= &my_charset_utf8mb4_bin; + utf8_val.copy(value, val_length, fromcs, tocs, &dummy_err); + + mysql_mutex_unlock(&LOCK_global_system_variables); + return 0; +} + +using Clone_Values= std::vector; +using Clone_Key_Values= std::vector>; + +int clone_get_charsets(MYSQL_THD thd, void *char_sets) +{ + auto charset_vals= static_cast(char_sets); + + for (CHARSET_INFO **cs= all_charsets; + cs < all_charsets + array_elements(all_charsets); cs++) + { + CHARSET_INFO *tmp_cs= cs[0]; + if (tmp_cs && (tmp_cs->state & MY_CS_PRIMARY) && + (tmp_cs->state & MY_CS_AVAILABLE)) + { + std::string charset; + /* Set the collation name. */ + charset.assign(tmp_cs->coll_name.str, tmp_cs->coll_name.length); + charset_vals->push_back(charset); + } + } + return 0; +} + +int clone_validate_charsets(MYSQL_THD thd, void *char_sets) +{ + if (!thd) + return 0; + auto charset_vals= static_cast(char_sets); + int last_error = 0; + + for (auto &char_set : *charset_vals) + { + auto charset_obj= get_charset_by_name(char_set.c_str(), MYF(0)); + + /* Check if character set collation is available. */ + if (!charset_obj) + { + my_error(ER_CLONE_CHARSET, MYF(0), char_set.c_str()); + /* Continue and check for all other errors. */ + last_error= ER_CLONE_CHARSET; + } + } + return last_error; +} + +int clone_get_configs(THD * thd, void *configs) +{ + int err= 0; + auto key_vals= static_cast(configs); + + for (auto &key_val : *key_vals) + { + String utf8_str; + auto &config_name= key_val.first; + err = get_utf8_config(thd, config_name, utf8_str); + + if (err != 0) + break; + + auto &config_val= key_val.second; + config_val.assign(utf8_str.c_ptr_quick()); + } + return err; +} + +/** + Says whether a character is a digit or a dot. + @param c character + @return true if c is a digit or a dot, otherwise false + */ +static bool is_digit_or_dot(char c) { return std::isdigit(c) || c == '.'; } + +/** + Compares versions, ignoring suffixes, i.e. 8.0.25 should be the same + as 8.0.25-debug, but 8.0.25 isn't the same as 8.0.251. + @param ver1 version1 string + @param ver2 version2 string + @return true if versions match (ignoring suffixes), false otherwise + */ +static bool compare_prefix_version(std::string ver1, std::string ver2) +{ + size_t i; + /* we iterate over both versions */ + for (i= 0; i < ver1.size() && i < ver2.size(); i++) + { + if (!is_digit_or_dot(ver1[i])) + /* If in one version we have something else than digit or dot, + we check what's in other version - if we also have a suffix or still + a version. */ + return !is_digit_or_dot(ver2[i]); + + /* We still compare version, and have a difference */ + if (ver1[i] != ver2[i]) return false; + } + if (i < ver1.size()) + /* we finished iterate over ver2, but still have some digits in ver1 */ + return !std::isdigit(ver1[i]); + + if (i < ver2.size()) + /* we finished iterate over ver1, but still have some digits in ver2 */ + return !std::isdigit(ver2[i]); + + return true; +} + +int clone_validate_configs(MYSQL_THD thd, void *configs) +{ + auto key_vals= static_cast(configs); + int last_error= 0; + + for (auto &key_val : *key_vals) + { + String utf8_str; + auto &config_name = key_val.first; + auto config_err = get_utf8_config(thd, config_name, utf8_str); + + if (config_err != 0) + { + last_error= config_err; + /* Continue and check for all other errors. */ + continue; + } + + auto &donor_val= key_val.second; + std::string config_val; + config_val.assign(utf8_str.c_ptr_quick()); + + /* Check if the parameter value matches. */ + if (config_val == donor_val) + continue; + + int critical_error= 0; + + /* Throw specific error for some configurations. These errors are critical + because user can no way clone from the current donor. */ + if (config_name.compare("version_compile_os") == 0) + critical_error = ER_CLONE_OS; + else if (config_name.compare("version") == 0) + { + /* we want to allow to add some suffix to the version and still match + i.e. 8.0.25 should be the same as 8.0.25-debug */ + if (compare_prefix_version(config_val, donor_val)) { + continue; + } + critical_error = ER_CLONE_DONOR_VERSION; + } + else if (config_name.compare("version_compile_machine") == 0) + critical_error = ER_CLONE_PLATFORM; + + /* For critical errors, exit immediately. */ + if (critical_error != 0) + { + last_error= critical_error; + my_error(last_error, MYF(0), donor_val.c_str(), config_val.c_str()); + break; + } + + last_error= ER_CLONE_CONFIG; + my_error(ER_CLONE_CONFIG, MYF(0), config_name.c_str(), donor_val.c_str(), + config_val.c_str()); + /* Continue and check for all other configuration mismatch. */ + } + return last_error; +} + +int clone_set_backup_stage(MYSQL_THD thd, uchar stage) +{ + return run_backup_stage(thd, static_cast(stage)); +} + +int clone_backup_lock(MYSQL_THD thd, const char *db, + const char *tbl) +{ + MDL_request request; + MDL_REQUEST_INIT(&request,MDL_key::TABLE, db, tbl, + MDL_SHARED_HIGH_PRIO, MDL_EXPLICIT); + if (thd->mdl_context.acquire_lock(&request, + thd->variables.lock_wait_timeout)) + return 1; + thd->mdl_backup_lock = request.ticket; + return 0; +} + +int clone_backup_unlock(MYSQL_THD thd) +{ + if (thd->mdl_backup_lock) + thd->mdl_context.release_lock(thd->mdl_backup_lock); + thd->mdl_backup_lock= 0; + return 0; +} diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 9d4b174fdaddc..de1a6dd5e0504 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12360,3 +12360,71 @@ ER_WARN_CONFLICTING_COMPOUND_INDEX_HINT_FOR_TABLE eng "Hint %s is ignored as conflicting/duplicated (an index hint of the same type or opposite kind has already been specified for this table)" ER_WARN_CONFLICTING_COMPOUND_INDEX_HINT_FOR_KEY eng "Hint %s is ignored as conflicting/duplicated (an index hint of the same type or opposite kind has already been specified for the key)" +ER_CLONE_DDL_IN_PROGRESS + eng "Concurrent DDL is performed during clone operation. Please try again." +ER_CLONE_TOO_MANY_CONCURRENT_CLONES + eng "Too many concurrent clone operations. Maximum allowed - %d." +ER_CLONE_DONOR + eng "Clone Donor Error: %.512s." +ER_CLONE_PROTOCOL + eng "Clone received unexpected response from Donor : %.512s." +ER_CLONE_DONOR_VERSION + eng "Clone Donor MySQL version: %.64s is different from Recipient MySQL version %.64s." +ER_CLONE_OS + eng "Clone Donor OS: %.64s is different from Recipient OS: %.64s." +ER_CLONE_PLATFORM + eng "Clone Donor platform: %.64s is different from Recipient platform: %.64s." +ER_CLONE_CHARSET + eng "Clone Donor collation: %.128s is unavailable in Recipient." +ER_CLONE_CONFIG + eng "Clone Configuration %.128s: Donor value: %.128s is different from Recipient value: %.128s." +ER_CLONE_SYS_CONFIG + eng "Clone system configuration: %.512s" +ER_CLONE_PLUGIN_MATCH + eng "Clone Donor plugin %.128s is not active in Recipient." +ER_CLONE_LOOPBACK + eng "Clone cannot use loop back connection while cloning into current data directory." +ER_CLONE_ENCRYPTION + eng "Clone needs SSL connection for encrypted table." +ER_CLONE_DISK_SPACE + eng "Clone estimated database size is %.64s. Available space %.64s is not enough." +ER_CLONE_IN_PROGRESS + eng "Concurrent clone in progress. Please try after clone is complete." +ER_CLONE_DISALLOWED + eng "The clone operation cannot be executed when %s." +ER_CLONE_NETWORK_PACKET + eng "Clone needs max_allowed_packet value to be %u or more. Current value is %u" +ER_CLONE_PLUGIN_NOT_LOADED_TRACE + eng "Clone plugin cannot be loaded." +ER_CLONE_HANDLER_EXIST_TRACE + eng "Clone Handler exists." +ER_CLONE_CREATE_HANDLER_FAIL_TRACE + eng "Could not create Clone Handler." +ER_CLONE_DONOR_TRACE + eng "Clone donor reported : %.512s." +ER_CLONE_PROTOCOL_TRACE + eng "Clone received unexpected response from donor : %.512s." +ER_CLONE_CLIENT_TRACE + eng "Clone Client: %.512s." +ER_CLONE_SERVER_TRACE + eng "Clone Server: %.512s." +ER_CLONE_SHUTDOWN_TRACE + eng "Clone shutting down server as RESTART failed. Please start server to complete clone operation." +ER_PATH_IN_DATADIR + eng "Path is within the current data directory '%-.192s'" +ER_IB_MSG_PAGE_ARCH_NO_RESET_POINTS + eng "Could not find appropriate reset points." +ER_IB_WRN_PAGE_ARCH_FLUSH_DATA + eng "Unable to flush. Page archiving data may be corrupt in case of a crash." +ER_IB_ERR_PAGE_ARCH_INVALID_DOUBLE_WRITE_BUF + eng "Page archiver's doublewrite buffer for %ld is not valid." +ER_IB_ERR_PAGE_ARCH_RECOVERY_FAILED + eng "Page archiver system's recovery failed." +ER_IB_ERR_PAGE_ARCH_INVALID_FORMAT + eng "Invalid archived file name format. The archived file is supposed to have the format %s + [0-9]*." +ER_PAGE_TRACKING_NOT_STARTED + eng "Page Tracking is not started yet." +ER_PAGE_TRACKING_RANGE_NOT_TRACKED + eng "Tracking was not enabled for the LSN range specified" +ER_PAGE_TRACKING_CANNOT_PURGE + eng "Cannot purge data when concurrent clone is in progress. Try later." diff --git a/sql/sql_admin.cc b/sql/sql_admin.cc index f9efa13d6a7df..04b1a76eec29b 100644 --- a/sql/sql_admin.cc +++ b/sql/sql_admin.cc @@ -35,6 +35,7 @@ #ifdef WITH_WSREP #include "wsrep_trans_observer.h" #endif +#include "clone_handler.h" const LEX_CSTRING msg_status= {STRING_WITH_LEN("status")}; const LEX_CSTRING msg_repair= { STRING_WITH_LEN("repair") }; @@ -1745,3 +1746,240 @@ bool Sql_cmd_repair_table::execute(THD *thd) error: DBUG_RETURN(res); } + +Sql_cmd_clone::Sql_cmd_clone(LEX_USER *user_info, ulong port, + LEX_CSTRING data_dir) + : m_port(port), m_data_dir(data_dir), m_clone(), m_is_local(false) +{ + m_host = user_info->host; + m_user = user_info->user; + m_passwd = user_info->auth->pwtext; +} + +bool Sql_cmd_clone::execute(THD *thd) +{ +#ifdef EMBEDDED_LIBRARY + my_error(ER_NOT_SUPPORTED_YET, MYF(0), + "Remote clone or REPLACE clone"); + return true; +#else + const bool is_replace= (m_data_dir.str == nullptr); + if (is_replace || !is_local()) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), + "Remote clone or REPLACE clone"); + return true; + } + + if (is_local()) + DBUG_PRINT("admin", ("CLONE type = local, DIR = %s", m_data_dir.str)); + else + DBUG_PRINT("admin", ("CLONE type = remote, DIR = %s", + is_replace ? "" : m_data_dir.str)); + + /* For replacing current data directory, needs clone_admin privilege. */ + if (is_replace) + { + /* TODO: Check for CLONE_ADMIN equivalent privilege. */ + if (check_global_access(thd, RELOAD_ACL) || + check_global_access(thd, LOCK_TABLES_ACL)) + return true; + } + else if (check_global_access(thd, RELOAD_ACL) || + check_global_access(thd, LOCK_TABLES_ACL)) + return true; + + assert(m_clone == nullptr); + m_clone= clone_plugin_lock(thd, &m_plugin); + + if (m_clone == nullptr) + { + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), "clone"); + return true; + } + + if (is_local()) + { + assert(!is_replace); + auto err= m_clone->clone_local(thd, m_data_dir.str); + + if (err != 0) + return true; + + my_ok(thd); + return false; + } + + assert(!is_local()); + + int ssl_mode= 1; + + if (thd->lex->account_options.ssl_type == SSL_TYPE_NONE) + ssl_mode= 0; + + auto err= m_clone->clone_remote_client( + thd, m_host.str, static_cast(m_port), m_user.str, m_passwd.str, + m_data_dir.str, ssl_mode); + clone_plugin_unlock(thd, m_plugin); + m_clone= nullptr; + + /* Set active VIO as clone plugin might have reset it */ + thd->set_active_vio(thd->net.vio); + + if (err != 0) + { + /* Log donor error number and message. */ + if (err == ER_CLONE_DONOR) + { + const char *donor_mesg= nullptr; + int donor_error= 0; + const bool success= + Clone_handler::get_donor_error(donor_error, donor_mesg); + if (success && donor_error != 0 && donor_mesg != nullptr) + { + char info_mesg[128]; + snprintf(info_mesg, 128, "Clone Donor error : %d : %s", donor_error, + donor_mesg); + const char* format= my_get_err_msg(ER_CLONE_CLIENT_TRACE); + sql_print_information(format, info_mesg); + } + } + return true; + } + + /* Check for KILL after setting active VIO */ + if (!is_replace && thd->killed != NOT_KILLED) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return true; + } + + /* Restart server after successfully cloning to current data directory. */ + if (is_replace) + { + /* Shutdown server if restart failed. */ + const char* mesg= my_get_err_msg(ER_CLONE_SHUTDOWN_TRACE); + sql_print_information("%s", mesg); + + Diagnostics_area *stmt_da= thd->get_stmt_da(); + Diagnostics_area shutdown_da(thd->query_id, false, true); + thd->set_stmt_da(&shutdown_da); + /* CLONE_ADMIN privilege allows us to shutdown/restart at end. */ + kill_mysql(thd); + thd->set_stmt_da(stmt_da); + return true; + } + my_ok(thd); +#endif /* EMBEDDED_LIBRARY */ + return false; +} + + +bool Sql_cmd_clone::load(THD *thd) +{ +#ifndef EMBEDDED_LIBRARY + assert(m_clone == nullptr); + assert(!is_local()); + + if (check_global_access(thd, RELOAD_ACL)) { + return true; + } + + m_clone = clone_plugin_lock(thd, &m_plugin); + + if (m_clone == nullptr) { + my_error(ER_PLUGIN_IS_NOT_LOADED, MYF(0), "clone"); + return true; + } +#endif /* EMBEDDED_SERVER */ + my_ok(thd); + return false; +} + +bool Sql_cmd_clone::execute_server(THD *thd) +{ +#ifndef EMBEDDED_LIBRARY + assert(!is_local()); + + auto net= &thd->net; + auto sock= net->vio->mysql_socket; + + Diagnostics_area *stmt_da= thd->get_stmt_da(); + Diagnostics_area clone_da(thd->query_id, false, true); + thd->set_stmt_da(&clone_da); + + auto err= m_clone->clone_remote_server(thd, sock); + + if (!err) + my_ok(thd); + + thd->set_stmt_da(stmt_da); + + if (err) + { + uint sql_errno= clone_da.sql_errno(); + const char *message= clone_da.message(); + const char *sqlstate= clone_da.get_sqlstate(); + + stmt_da->set_overwrite_status(true); + + if (unlikely(thd->is_fatal_error)) + stmt_da->set_error_status(sql_errno, message, sqlstate, nullptr); + else + stmt_da->push_warning(thd, sql_errno, sqlstate, + Sql_condition::WARN_LEVEL_ERROR, message); + } + + clone_plugin_unlock(thd, m_plugin); + m_clone = nullptr; + + return err != 0; +#else + return 0; +#endif /* EMBEDDED_LIBRARY */ +} + +/* TODO: Interface to rewrite Statement with plain-text password */ +bool Sql_cmd_clone::rewrite(THD *thd, String &rlb) +{ + /* No password for local clone. */ + if (is_local()) { + return false; + } + + bool no_bs= thd->variables.sql_mode & MODE_NO_BACKSLASH_ESCAPES; + rlb.append(STRING_WITH_LEN("CLONE INSTANCE FROM ")); + + /* Append user name. */ + append_query_string(system_charset_info, &rlb, m_user.str, m_user.length, + no_bs); + /* Append host name. */ + rlb.append(STRING_WITH_LEN("@")); + append_query_string(system_charset_info, &rlb, m_host.str, m_host.length, + no_bs); + + /* Append port number. */ + rlb.append(STRING_WITH_LEN(":")); + String num_buffer(42); + num_buffer.set((longlong)m_port, &my_charset_bin); + rlb.append(num_buffer); + + /* Append password clause. */ + rlb.append(STRING_WITH_LEN(" IDENTIFIED BY ")); + + /* Append data directory clause. */ + if (m_data_dir.str != nullptr) { + rlb.append(STRING_WITH_LEN(" DATA DIRECTORY = ")); + append_query_string(system_charset_info, &rlb, m_data_dir.str, + m_data_dir.length, no_bs); + } + + /* Append SSL information. */ + if (thd->lex->account_options.ssl_type == SSL_TYPE_NONE) { + rlb.append(STRING_WITH_LEN(" REQUIRE NO SSL")); + + } else if (thd->lex->account_options.ssl_type == SSL_TYPE_SPECIFIED) { + rlb.append(STRING_WITH_LEN(" REQUIRE SSL")); + } + return true; +} diff --git a/sql/sql_admin.h b/sql/sql_admin.h index ccccef748c1a2..d233a88d5acd9 100644 --- a/sql/sql_admin.h +++ b/sql/sql_admin.h @@ -114,4 +114,86 @@ class Sql_cmd_repair_table : public Sql_cmd } }; +/** + Sql_cmd_clone implements CLONE ... statement. +*/ +class Clone_handler; + +class Sql_cmd_clone : public Sql_cmd { + public: + /** Construct clone command for clone server */ + explicit Sql_cmd_clone() + : m_host(), + m_port(), + m_user(), + m_passwd(), + m_data_dir(), + m_clone(), + m_is_local(false) {} + + /** Construct clone command for clone client + @param[in] user_info user, password and remote host information + @param[in] port port for remote server + @param[in] data_dir data directory to clone */ + explicit Sql_cmd_clone(LEX_USER *user_info, ulong port, LEX_CSTRING data_dir); + + /** Construct clone command for local clone + @param[in] data_dir data directory to clone */ + explicit Sql_cmd_clone(LEX_CSTRING data_dir) + : m_host(), + m_port(), + m_user(), + m_passwd(), + m_data_dir(data_dir), + m_clone(), + m_is_local(true) {} + + enum_sql_command sql_command_code() const override { return SQLCOM_CLONE; } + + bool execute(THD *thd) override; + + /** Execute clone server. + @param[in] thd server session + @return true, if error */ + bool execute_server(THD *thd); + + /** Load clone plugin for clone server. + @param[in] thd server session + @return true, if error */ + bool load(THD *thd); + + /** Re-write clone statement to hide password. + @param[in,out] thd server session + @param[in,out] rlb the buffer to return the rewritten query in. empty if none. + @return true iff query is re-written */ + bool rewrite(THD *thd, String &rlb); + + /** @return true, if it is local clone command */ + bool is_local() const { return (m_is_local); } + + private: + /** Remote server IP */ + LEX_CSTRING m_host; + + /** Remote server port */ + const ulong m_port; + + /** User name for remote connection */ + LEX_CSTRING m_user; + + /** Password for remote connection */ + LEX_CSTRING m_passwd; + + /** Data directory for cloned data */ + LEX_CSTRING m_data_dir; + + /** Clone handle in server */ + Clone_handler *m_clone; + + /** Loaded clone plugin reference */ + plugin_ref m_plugin; + + /** If it is local clone operation */ + bool m_is_local; +}; #endif diff --git a/sql/sql_class.cc b/sql/sql_class.cc index fcfb219870b68..7eb249972f6cd 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -895,7 +895,8 @@ THD::THD(my_thread_id id, bool is_wsrep_applier) &variables.wt_deadlock_search_depth_long, &variables.wt_timeout_long); #ifdef SIGNAL_WITH_VIO_CLOSE - active_vio = 0; + active_vio= 0; + clone_vio= 0; #endif mysql_mutex_init(key_LOCK_thd_data, &LOCK_thd_data, MY_MUTEX_INIT_FAST); mysql_mutex_init(key_LOCK_wakeup_ready, &LOCK_wakeup_ready, MY_MUTEX_INIT_FAST); @@ -1816,7 +1817,8 @@ void THD::reset_for_reuse() profiling.reset(); #endif #ifdef SIGNAL_WITH_VIO_CLOSE - active_vio = 0; + active_vio= 0; + clone_vio= 0; #endif #ifdef WITH_WSREP wsrep_free_status(this); @@ -2079,6 +2081,8 @@ void THD::awake_no_mutex(killed_state state_to_set) { if(active_vio) vio_shutdown(active_vio, SHUT_RDWR); + if(clone_vio) + vio_shutdown(clone_vio, SHUT_RDWR); } #endif @@ -2188,6 +2192,7 @@ void THD::disconnect() */ vio= active_vio; close_active_vio(); + close_clone_vio(); #endif /* Disconnect even if a active vio is not associated. */ @@ -3069,6 +3074,20 @@ void THD::close_active_vio() #endif DBUG_VOID_RETURN; } + +void THD::close_clone_vio() +{ + DBUG_ENTER("close_clone_vio"); + mysql_mutex_assert_owner(&LOCK_thd_data); +#ifndef EMBEDDED_LIBRARY + if (clone_vio) + { + vio_close(clone_vio); + clone_vio = 0; + } +#endif + DBUG_VOID_RETURN; +} #endif diff --git a/sql/sql_class.h b/sql/sql_class.h index 7daa28e74e7fd..282c160a29760 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3651,6 +3651,8 @@ class THD: public THD_count, /* this must be first */ #endif #ifdef SIGNAL_WITH_VIO_CLOSE Vio* active_vio; + /* Active network vio for clone remote connection. */ + Vio *clone_vio; #endif /* @@ -4366,7 +4368,25 @@ class THD: public THD_count, /* this must be first */ active_vio = 0; mysql_mutex_unlock(&LOCK_thd_data); } + void close_active_vio(); + + /** Set active clone network Vio for remote clone. + @param[in] vio network vio */ + inline void set_clone_vio(Vio *vio) { + mysql_mutex_lock(&LOCK_thd_data); + clone_vio = vio; + mysql_mutex_unlock(&LOCK_thd_data); + } + + /** Clear clone network Vio for remote clone. */ + inline void clear_clone_vio() { + mysql_mutex_lock(&LOCK_thd_data); + clone_vio = nullptr; + mysql_mutex_unlock(&LOCK_thd_data); + } + + void close_clone_vio(); #endif void awake_no_mutex(killed_state state_to_set); void awake(killed_state state_to_set) diff --git a/sql/sql_cmd.h b/sql/sql_cmd.h index dd55e8ecee9f0..0efb52fbc0887 100644 --- a/sql/sql_cmd.h +++ b/sql/sql_cmd.h @@ -112,6 +112,7 @@ enum enum_sql_command { SQLCOM_SHOW_PACKAGE_BODY_CODE, SQLCOM_BACKUP, SQLCOM_BACKUP_LOCK, SQLCOM_SHOW_CREATE_SERVER, + SQLCOM_CLONE, /* When a command is added here, be sure it's also added in mysqld.cc diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index b5df93010a553..d051a58e1c356 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -162,7 +162,7 @@ const LEX_CSTRING command_name[257]={ { STRING_WITH_LEN("Daemon") }, //29 { STRING_WITH_LEN("Unimpl get tid") }, //30 { STRING_WITH_LEN("Reset connection") },//31 - { 0, 0 }, //32 + { STRING_WITH_LEN("Remote Clone") }, //32 { 0, 0 }, //33 { 0, 0 }, //34 { 0, 0 }, //35 @@ -775,6 +775,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_DROP_SERVER]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_BACKUP]= CF_AUTO_COMMIT_TRANS; sql_command_flags[SQLCOM_BACKUP_LOCK]= CF_AUTO_COMMIT_TRANS; + sql_command_flags[SQLCOM_CLONE]= CF_AUTO_COMMIT_TRANS; /* The following statements can deal with temporary tables, @@ -884,6 +885,7 @@ void init_update_queries(void) sql_command_flags[SQLCOM_REVOKE_ALL]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_INSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS; sql_command_flags[SQLCOM_UNINSTALL_PLUGIN]|= CF_DISALLOW_IN_RO_TRANS; + sql_command_flags[SQLCOM_CLONE]|= CF_DISALLOW_IN_RO_TRANS; #ifdef WITH_WSREP /* Statements for which some errors are ignored when @@ -1598,6 +1600,7 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD * command_name[command].str : ""))); bool drop_more_results= 0; + Sql_cmd_clone *clone_cmd = nullptr; if (thd->async_state.m_state == thd_async_state::enum_async_state::RESUMED) { @@ -1722,6 +1725,21 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD * my_ok(thd, 0, 0, 0); break; } + case COM_CLONE: { + status_var_increment(thd->status_var.com_other); + + /* Try loading clone plugin */ + clone_cmd = new (thd->mem_root) Sql_cmd_clone(); + + if (clone_cmd && clone_cmd->load(thd)) { + clone_cmd = nullptr; + } + + thd->lex->m_sql_cmd = clone_cmd; + thd->lex->sql_command = SQLCOM_CLONE; + + break; + } case COM_CHANGE_USER: { int auth_rc; @@ -2427,6 +2445,13 @@ dispatch_command_return dispatch_command(enum enum_server_command command, THD * thd->protocol->end_statement(); query_cache_end_of_result(thd); } + + /* After sending response, switch to clone protocol */ + if (clone_cmd != nullptr) { + assert(command == COM_CLONE); + error = clone_cmd->execute_server(thd); + } + if (drop_more_results) thd->server_status&= ~SERVER_MORE_RESULTS_EXISTS; @@ -5858,6 +5883,12 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) DBUG_PRINT("result", ("res: %d killed: %d is_error(): %d", res, thd->killed, thd->is_error())); break; + case SQLCOM_CLONE: + { + assert(lex->m_sql_cmd != nullptr); + res = lex->m_sql_cmd->execute(thd); + break; + } default: #ifndef EMBEDDED_LIBRARY diff --git a/sql/sql_plugin.cc b/sql/sql_plugin.cc index c71dbf8802636..1a5bafd7c9737 100644 --- a/sql/sql_plugin.cc +++ b/sql/sql_plugin.cc @@ -35,6 +35,7 @@ #include #include "lock.h" // MYSQL_LOCK_IGNORE_TIMEOUT #include +#include #include #include #include @@ -99,7 +100,8 @@ const LEX_CSTRING plugin_type_names[MYSQL_MAX_PLUGIN_TYPE_NUM]= { STRING_WITH_LEN("PASSWORD VALIDATION") }, { STRING_WITH_LEN("ENCRYPTION") }, { STRING_WITH_LEN("DATA TYPE") }, - { STRING_WITH_LEN("FUNCTION") } + { STRING_WITH_LEN("FUNCTION") }, + { STRING_WITH_LEN("CLONE") } }; extern int initialize_schema_table(void *plugin); @@ -150,7 +152,8 @@ static int plugin_type_initialization_order[MYSQL_MAX_PLUGIN_TYPE_NUM]= MariaDB_PASSWORD_VALIDATION_PLUGIN, MYSQL_AUDIT_PLUGIN, MYSQL_REPLICATION_PLUGIN, - MYSQL_UDF_PLUGIN + MYSQL_UDF_PLUGIN, + MariaDB_CLONE_PLUGIN }; #ifdef HAVE_DLOPEN @@ -186,7 +189,8 @@ static int min_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MariaDB_PASSWORD_VALIDATION_INTERFACE_VERSION, MariaDB_ENCRYPTION_INTERFACE_VERSION, MariaDB_DATA_TYPE_INTERFACE_VERSION, - MariaDB_FUNCTION_INTERFACE_VERSION + MariaDB_FUNCTION_INTERFACE_VERSION, + MariaDB_CLONE_INTERFACE_VERSION }; static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= { @@ -201,7 +205,8 @@ static int cur_plugin_info_interface_version[MYSQL_MAX_PLUGIN_TYPE_NUM]= MariaDB_PASSWORD_VALIDATION_INTERFACE_VERSION, MariaDB_ENCRYPTION_INTERFACE_VERSION, MariaDB_DATA_TYPE_INTERFACE_VERSION, - MariaDB_FUNCTION_INTERFACE_VERSION + MariaDB_FUNCTION_INTERFACE_VERSION, + MariaDB_CLONE_INTERFACE_VERSION }; static struct diff --git a/sql/sql_plugin_services.inl b/sql/sql_plugin_services.inl index ff43bcb2ff331..936c0e85841ce 100644 --- a/sql/sql_plugin_services.inl +++ b/sql/sql_plugin_services.inl @@ -341,10 +341,34 @@ static struct provider_service_lz4_st provider_handler_lz4= }; struct provider_service_lz4_st *provider_service_lz4= &provider_handler_lz4; +static struct clone_protocol_service_st clone_protocol_handler= { +#ifndef EMBEDDED_LIBRARY + clone_start_statement, + clone_finish_statement, + clone_get_charsets, + clone_validate_charsets, + clone_get_configs, + clone_validate_configs, + clone_connect, + clone_send_command, + clone_get_response, + clone_kill, + clone_disconnect, + clone_get_error, + clone_get_command, + clone_send_response, + clone_send_error, + clone_set_backup_stage, + clone_backup_lock, + clone_backup_unlock +#endif /* EMBEDDED_LIBRARY */ +}; + static struct st_service_ref list_of_services[]= { { "base64_service", VERSION_base64, &base64_handler }, { "debug_sync_service", VERSION_debug_sync, 0 }, // updated in plugin_init() + { "clone_protocol_service", VERSION_clone_protocol, &clone_protocol_handler }, { "encryption_scheme_service", VERSION_encryption_scheme, &encryption_scheme_handler }, { "encryption_service", VERSION_encryption, &encryption_handler }, { "logger_service", VERSION_logger, &logger_service_handler }, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 11675d88101e4..41f930d0d318a 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -799,6 +799,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token CLIENT_SYM %token CLOB_MARIADB_SYM /* SQL-2003-R */ %token CLOB_ORACLE_SYM /* Oracle-R */ +%token CLONE_SYM /* MYSQL */ %token CLOSE_SYM /* SQL-2003-R */ %token COALESCE /* SQL-2003-N */ %token CODE_SYM @@ -912,6 +913,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); %token INDEXES %token INSERT_METHOD %token INSTALL_SYM +%token INSTANCE_SYM /* MySQL */ %token INVOKER_SYM %token IO_SYM %token IPC_SYM @@ -1338,7 +1340,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, size_t *yystacksize); sp_opt_label BIN_NUM TEXT_STRING_filesystem opt_constraint constraint opt_ident sp_block_label sp_control_label opt_place opt_db - udt_name + udt_name opt_datadir_ssl %ifdef ORACLE %type @@ -1883,7 +1885,7 @@ rule: opt_constraint_no_id json_table_columns_clause json_table_columns_list json_table_column json_table_column_type json_opt_on_empty_or_error - json_on_error_response json_on_empty_response + json_on_error_response json_on_empty_response clone_stmt %type call sp_proc_stmts sp_proc_stmts1 sp_proc_stmt %type sp_if_then_statements sp_case_then_statements @@ -2138,6 +2140,7 @@ verb_clause: | change | check | checksum + | clone_stmt | commit | create | deallocate @@ -8949,6 +8952,61 @@ opt_ignore_leaves: | IGNORE_SYM LEAVES { $$= TL_OPTION_IGNORE_LEAVES; } ; +/* Clone local/remote replica statements. */ +clone_stmt: + CLONE_SYM LOCAL_SYM + DATA_SYM DIRECTORY_SYM opt_equal TEXT_STRING_filesystem + { + Lex->sql_command= SQLCOM_CLONE; + Lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_clone($6); + if (Lex->m_sql_cmd == nullptr) + MYSQL_YYABORT; + } + + | CLONE_SYM INSTANCE_SYM FROM user ':' ulong_num + IDENTIFIED_SYM BY TEXT_STRING + opt_datadir_ssl + { + Lex->sql_command= SQLCOM_CLONE; + /* TODO: Reject space characters around ':' */ + $4->auth= new (thd->mem_root) USER_AUTH(); + $4->auth->pwtext= $9; + + Lex->m_sql_cmd= new (thd->mem_root) + Sql_cmd_clone($4, $6, $10); + + if (Lex->m_sql_cmd == nullptr) + MYSQL_YYABORT; + } + ; + +opt_datadir_ssl: + opt_ssl + { + $$= null_clex_str; + } + | DATA_SYM DIRECTORY_SYM opt_equal TEXT_STRING_filesystem opt_ssl + { + $$= $4; + } + ; + +opt_ssl: + /* empty */ + { + Lex->account_options.ssl_type= SSL_TYPE_NOT_SPECIFIED; + } + | REQUIRE_SYM SSL_SYM + { + Lex->account_options.ssl_type= SSL_TYPE_SPECIFIED; + } + | REQUIRE_SYM NO_SYM SSL_SYM + { + Lex->account_options.ssl_type= SSL_TYPE_NONE; + } + ; + /* Select : retrieve data from table */ @@ -16450,6 +16508,7 @@ keyword_sp_var_not_label: | HELP_SYM | HOST_SYM | INSTALL_SYM + | INSTANCE_SYM | OPTION | OPTIONS_SYM | OTHERS_MARIADB_SYM @@ -16534,7 +16593,8 @@ keyword_sp_head: xxx:=10 */ keyword_verb_clause: - CLOSE_SYM /* Verb clause. Reserved in Oracle */ + CLONE_SYM /* Verb clause. Reserved in Oracle */ + | CLOSE_SYM /* Verb clause. Reserved in Oracle */ | COMMIT_SYM /* Verb clause. Reserved in Oracle */ | DO_SYM /* Verb clause */ | HANDLER_SYM /* Verb clause */ diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index 3204b66721b17..908e2c004ac6a 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -91,6 +91,8 @@ IF (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion -Wno-sign-conversion") SET_SOURCE_FILES_PROPERTIES(fts/fts0pars.cc PROPERTIES COMPILE_FLAGS -Wno-conversion) + SET_SOURCE_FILES_PROPERTIES(clone/clone0api.cc + PROPERTIES COMPILE_FLAGS -Wno-conversion) ENDIF() IF(NOT MSVC) @@ -133,6 +135,10 @@ INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/storage/innobase/include ${CMAKE_SOURCE_DIR}/libbinlogevents/include) SET(INNOBASE_SOURCES + arch/arch0arch.cc + arch/arch0page.cc + arch/arch0log.cc + arch/arch0recv.cc btr/btr0btr.cc btr/btr0bulk.cc btr/btr0cur.cc @@ -146,6 +152,12 @@ SET(INNOBASE_SOURCES buf/buf0flu.cc buf/buf0lru.cc buf/buf0rea.cc + clone/clone0api.cc + clone/clone0clone.cc + clone/clone0copy.cc + clone/clone0apply.cc + clone/clone0desc.cc + clone/clone0snapshot.cc data/data0data.cc data/data0type.cc dict/dict0boot.cc diff --git a/storage/innobase/arch/arch0arch.cc b/storage/innobase/arch/arch0arch.cc new file mode 100644 index 0000000000000..9c6a1c9759f79 --- /dev/null +++ b/storage/innobase/arch/arch0arch.cc @@ -0,0 +1,640 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file arch/arch0arch.cc + Common implementation for redo log and dirty page archiver system + + *******************************************************/ + +#include "arch0arch.h" +#include "arch0page.h" +#include "srv0start.h" +#include "log.h" +#include "univ.i" + +extern void ignore_db_dirs_append(const char *dirname_arg); + + +#ifdef UNIV_PFS_THREAD +/** PFS thread key for log archiver background. */ +mysql_pfs_key_t archiver_thread_key; +#endif /* UNIV_PFS_THREAD */ + +/** Archiver system global */ +Arch_Sys *arch_sys= nullptr; + +dberr_t Arch_Sys::init() +{ + if (!arch_sys) + arch_sys= UT_NEW(Arch_Sys(), mem_key_archive); + + ignore_db_dirs_append(ARCH_DIR); + return arch_sys ? DB_SUCCESS : DB_OUT_OF_MEMORY; +} + +void Arch_Sys::stop() +{ + /* To be called during shutown last phase. */ + ut_ad(srv_shutdown_state.load() >= SRV_SHUTDOWN_LAST_PHASE); + std::chrono::milliseconds sleep_time{1}; + + /* Start with 1ms and back off till 1 sec. */ + int sleep_count=0, backoff_count=0; + + while (arch_sys && arch_sys->signal_archiver()) + { + std::this_thread::sleep_for(sleep_time); + if (++sleep_count == 10 && backoff_count < 3) + { + sleep_time*= 10; + sleep_count= 0; + ++backoff_count; + continue; + } + if (sleep_count == 30 ) + ib::warn() << "Archiver still running: Waited 30 seconds."; + + else if (sleep_count >= 600) + ib::fatal() << "Archiver still running: Waited for 10 minutes."; + } +} + +void Arch_Sys::free() +{ + if (arch_sys) + { + UT_DELETE(arch_sys); + arch_sys= nullptr; + } +} + +int Arch_Sys::start_archiver() +{ + if (!os_file_create_directory(ARCH_DIR, false)) + { + my_error(ER_CANT_CREATE_FILE, MYF(0), ARCH_DIR, errno); + return ER_CANT_CREATE_FILE; + } + int err=0; + + mysql_mutex_lock(&m_mutex); + if (!m_archiver_active) + { + try + { + std::thread(Arch_Sys::archiver).detach(); + m_archiver_active= true; + } + catch (...) + { + my_error(ER_CANT_CREATE_THREAD, MYF(0), errno); + m_archiver_active= false; + err= ER_CANT_CREATE_THREAD; + } + } + mysql_mutex_unlock(&m_mutex); + return err; +} + +bool Arch_Sys::signal_archiver() +{ + bool alive= false; + mysql_mutex_lock(&m_mutex); + if (m_archiver_active) + { + mysql_cond_signal(&m_cond); + m_signalled= true; + alive= true; + } + mysql_mutex_unlock(&m_mutex); + return alive; +} + +void Arch_Sys::archiver_wait() +{ + mysql_mutex_lock(&m_mutex); + ut_ad(m_archiver_active); + + struct timespec wait_time; + while(!m_signalled) + { + set_timespec(wait_time, 1); + mysql_cond_timedwait(&m_cond, &m_mutex, &wait_time); + } + m_signalled= false; + mysql_mutex_unlock(&m_mutex); +} + +void Arch_Sys::archiver_stopped() +{ + mysql_mutex_lock(&m_mutex); + m_archiver_active= false; + mysql_mutex_unlock(&m_mutex); +} + +void Arch_Sys::remove_file(const char *file_path, const char *file_name) +{ + char path[MAX_ARCH_PAGE_FILE_NAME_LEN]; + + static_assert(MAX_ARCH_LOG_FILE_NAME_LEN <= MAX_ARCH_PAGE_FILE_NAME_LEN); + ut_ad(strlen(file_path) + 1 + strlen(file_name) < + MAX_ARCH_PAGE_FILE_NAME_LEN); + + /* Remove only LOG and PAGE archival files. */ + if (0 != strncmp(file_name, ARCH_LOG_FILE, strlen(ARCH_LOG_FILE)) && + 0 != strncmp(file_name, ARCH_PAGE_FILE, strlen(ARCH_PAGE_FILE)) && + 0 != strncmp(file_name, ARCH_PAGE_GROUP_DURABLE_FILE_NAME, + strlen(ARCH_PAGE_GROUP_DURABLE_FILE_NAME))) + return; + + snprintf(path, sizeof(path), "%s%c%s", file_path, OS_PATH_SEPARATOR, + file_name); + +#ifdef UNIV_DEBUG + os_file_type_t type; + bool exists; + + os_file_status(path, &exists, &type); + ut_ad(exists); + ut_ad(type == OS_FILE_TYPE_FILE); +#endif /* UNIV_DEBUG */ + + os_file_delete(innodb_arch_file_key, path); +} + +void Arch_Sys::remove_dir(const char *dir_path, const char *dir_name) +{ + char path[MAX_ARCH_DIR_NAME_LEN]; + + static_assert(sizeof(ARCH_LOG_DIR) <= sizeof(ARCH_PAGE_DIR)); + ut_ad(strlen(dir_path) + 1 + strlen(dir_name) + 1 < sizeof(path)); + + /* Remove only LOG and PAGE archival directories. */ + if (0 != strncmp(dir_name, ARCH_LOG_DIR, strlen(ARCH_LOG_DIR)) && + 0 != strncmp(dir_name, ARCH_PAGE_DIR, strlen(ARCH_PAGE_DIR))) + return; + + snprintf(path, sizeof(path), "%s%c%s", dir_path, OS_PATH_SEPARATOR, dir_name); + +#ifdef UNIV_DEBUG + os_file_type_t type; + bool exists; + + os_file_status(path, &exists, &type); + ut_ad(exists); + ut_ad(type == OS_FILE_TYPE_DIR); +#endif /* UNIV_DEBUG */ + + os_file_scan_directory(path, Arch_Sys::remove_file, true); +} + +/** Initialize Page and Log archiver system. */ +Arch_Sys::Arch_Sys() +{ + mysql_mutex_init(0, &m_mutex, nullptr); + mysql_cond_init(0, &m_cond, nullptr); + + m_signalled= false; + m_archiver_active= false; + + if (srv_read_only_mode) + m_page_sys.set_read_only_mode(); + else + m_page_sys.recover(); +} + +/** Free Page and Log archiver system */ +Arch_Sys::~Arch_Sys() +{ + mysql_cond_destroy(&m_cond); + mysql_mutex_destroy(&m_mutex); +} + +dberr_t Arch_Group::write_to_file(Arch_File_Ctx *from_file, byte *from_buffer, + uint length, bool partial_write, + bool do_persist) +{ + dberr_t err= DB_SUCCESS; + uint write_size; + + if (m_file_ctx.is_closed()) + { + /* First file in the archive group. */ + ut_ad(m_file_ctx.get_count() == 0); + DBUG_EXECUTE_IF("crash_before_archive_file_creation", DBUG_SUICIDE();); + + err= m_file_ctx.open_new(m_begin_lsn, m_file_size, m_header_len); + if (err != DB_SUCCESS) + return err; + } + + auto len_left= m_file_ctx.bytes_left(); + + /* New file is immediately opened when current file is over. */ + ut_ad(len_left != 0); + + while (length > 0) + { + auto len_copy= static_cast(length); + + /* Write as much as possible in current file. */ + if (len_left < len_copy) + { + ut_ad(len_left <= std::numeric_limits::max()); + write_size= static_cast(len_left); + } + else + write_size= length; + + if (do_persist) + { + Arch_Page_Dblwr_Offset dblwr_offset= + (partial_write ? ARCH_PAGE_DBLWR_PARTIAL_FLUSH_PAGE + : ARCH_PAGE_DBLWR_FULL_FLUSH_PAGE); + + Arch_Group::write_to_doublewrite_file(from_file, from_buffer, write_size, + dblwr_offset); + } + + if (partial_write) + { + DBUG_EXECUTE_IF("crash_after_partial_block_dblwr_flush", DBUG_SUICIDE();); + err= m_file_ctx.write(from_file, from_buffer, + static_cast(m_file_ctx.get_offset()), + write_size); + } + else + { + DBUG_EXECUTE_IF("crash_after_full_block_dblwr_flush", DBUG_SUICIDE();); + err= m_file_ctx.write(from_file, from_buffer, write_size); + } + if (err != DB_SUCCESS) + return (err); + + if (do_persist) + /* Flush the file to make sure the changes are made persistent as there + would be no way to recover the data otherwise in case of a crash. */ + m_file_ctx.flush(); + + ut_ad(length >= write_size); + length-= write_size; + + len_left= m_file_ctx.bytes_left(); + + /* Current file is over, switch to next file. */ + if (len_left == 0) + { + m_file_ctx.close(); + + err= m_file_ctx.open_new(m_begin_lsn, m_file_size, m_header_len); + if (err != DB_SUCCESS) + return (err); + + DBUG_EXECUTE_IF("crash_after_archive_file_creation", DBUG_SUICIDE();); + + len_left= m_file_ctx.bytes_left(); + } + } + return DB_SUCCESS; +} + +bool Arch_File_Ctx::delete_file(uint file_index, lsn_t begin_lsn) +{ + bool success; + char file_name[MAX_ARCH_PAGE_FILE_NAME_LEN]; + + build_name(file_index, begin_lsn, file_name, MAX_ARCH_PAGE_FILE_NAME_LEN); + + os_file_type_t type; + bool exists; + + success= os_file_status(file_name, &exists, &type); + if (!success || !exists) + return (false); + + ut_ad(type == OS_FILE_TYPE_FILE); + + success= os_file_delete(innodb_arch_file_key, file_name); + return success; +} + +void Arch_File_Ctx::delete_files(lsn_t begin_lsn) +{ + bool exists; + os_file_type_t type; + char dir_name[MAX_ARCH_DIR_NAME_LEN]; + + build_dir_name(begin_lsn, dir_name, MAX_ARCH_DIR_NAME_LEN); + os_file_status(dir_name, &exists, &type); + + if (exists) + { + ut_ad(type == OS_FILE_TYPE_DIR); + os_file_scan_directory(dir_name, Arch_Sys::remove_file, true); + } +} + +dberr_t Arch_File_Ctx::init(const char *path, const char *base_dir, + const char *base_file, uint num_files) +{ + m_base_len= static_cast(strlen(path)); + + m_name_len= + m_base_len + static_cast(strlen(base_file)) + MAX_LSN_DECIMAL_DIGIT; + + if (base_dir != nullptr) + { + m_name_len += static_cast(strlen(base_dir)); + m_name_len += MAX_LSN_DECIMAL_DIGIT; + } + + /* Add some extra buffer. */ + m_name_len+= MAX_LSN_DECIMAL_DIGIT; + + /* In case of reinitialise. */ + if (m_name_buf != nullptr) + { + ut_free(m_name_buf); + m_name_buf = nullptr; + } + m_name_buf= static_cast(ut_malloc(m_name_len, mem_key_archive)); + + if (m_name_buf == nullptr) + return DB_OUT_OF_MEMORY; + + m_path_name= path; + m_dir_name= base_dir; + m_file_name= base_file; + + strcpy(m_name_buf, path); + + if (m_name_buf[m_base_len - 1] != OS_PATH_SEPARATOR) + { + m_name_buf[m_base_len] = OS_PATH_SEPARATOR; + ++m_base_len; + m_name_buf[m_base_len]= '\0'; + } + + m_file.m_file= OS_FILE_CLOSED; + m_index= 0; + m_count= num_files; + m_offset= 0; + + m_reset.clear(); + m_stop_points.clear(); + + return DB_SUCCESS; +} + +dberr_t Arch_File_Ctx::open(bool read_only, lsn_t start_lsn, uint file_index, + uint64_t file_offset, uint64_t file_size) +{ + /* Close current file, if open. */ + close(); + m_index= file_index; + m_offset= file_offset; + + build_name(m_index, start_lsn, nullptr, 0); + + bool exists; + os_file_type_t type; + + bool success= os_file_status(m_name_buf, &exists, &type); + + if (!success) + return DB_CANNOT_OPEN_FILE; + + os_file_create_t option; + + if (read_only) + { + if (!exists) + return DB_CANNOT_OPEN_FILE; + option= OS_FILE_OPEN; + } + else + option= exists ? OS_FILE_OPEN : OS_FILE_CREATE; + + if (option == OS_FILE_CREATE) + /* In case of a failure, we would use the error from os_file_create. */ + std::ignore= os_file_create_subdirs_if_needed(m_name_buf); + + m_file= os_file_create(innodb_arch_file_key, m_name_buf, option, + OS_CLONE_LOG_FILE, read_only, &success); + + if (!success) + return DB_CANNOT_OPEN_FILE; + + if (success) + success= os_file_seek(m_name_buf, m_file.m_file, file_offset); + + m_size= file_size; + ut_ad(m_offset <= m_size); + + if (success) + return DB_SUCCESS; + + close(); + return DB_IO_ERROR; +} + +dberr_t Arch_File_Ctx::open_new(lsn_t start_lsn, uint64_t new_file_size, + uint64_t initial_file_size) +{ + auto err= open(false, start_lsn, m_count, initial_file_size, new_file_size); + if (err != DB_SUCCESS) + return err; + ++m_count; + return DB_SUCCESS; +} + +dberr_t Arch_File_Ctx::open_next(lsn_t start_lsn, uint64_t file_offset, + uint64_t file_size) +{ + m_index++; + /* Reopen the same file */ + if (m_index == m_count) m_index= 0; + + /* Open next file. */ + auto error= open(true, start_lsn, m_index, file_offset, file_size); + return error; +} + +dberr_t Arch_File_Ctx::read(byte *to_buffer, uint64_t offset, uint size) +{ + ut_ad(offset + size <= m_size); + ut_ad(!is_closed()); + + auto err= os_file_read(IORequestRead, m_file, to_buffer, offset, size, + nullptr); + return err; +} + +dberr_t Arch_File_Ctx::resize_and_overwrite_with_zeros(uint64_t file_size) +{ + ut_ad(m_size <= file_size); + m_size= file_size; + byte *buf= + static_cast(ut_zalloc((uint)file_size, mem_key_archive)); + + /* Make sure that the physical file size is the same as logical by filling + the file with all-zeroes. Page archiver recovery expects that the physical + file size is the same as logical file size. */ + const dberr_t err= write(nullptr, buf, 0, (uint)file_size); + + ut_free(buf); + + if (err != DB_SUCCESS) + return err; + + flush(); + return DB_SUCCESS; +} + +dberr_t Arch_File_Ctx::write(Arch_File_Ctx *from_file, byte *from_buffer, + uint size) +{ + dberr_t err; + + if (from_buffer == nullptr) + { + /* write from File */ + err= os_file_copy(from_file->m_file, from_file->m_offset, m_file, m_offset, + size); + + if (err == DB_SUCCESS) + { + from_file->m_offset+= size; + ut_ad(from_file->m_offset <= from_file->m_size); + } + + } + else + /* write from buffer */ + err= os_file_write(IORequestWrite, "Track file", m_file, from_buffer, + m_offset, size); + if (err != DB_SUCCESS) + return (err); + + m_offset+= size; + ut_ad(m_offset <= m_size); + + return DB_SUCCESS; +} + +void Arch_File_Ctx::build_name(uint idx, lsn_t dir_lsn, char *buffer, + uint length) +{ + char *buf_ptr; + uint buf_len; + + /* If user has passed NULL, use pre-allocated buffer. */ + if (buffer == nullptr) + { + buf_ptr= m_name_buf; + buf_len= m_name_len; + } + else + { + buf_ptr= buffer; + buf_len= length; + strncpy(buf_ptr, m_name_buf, buf_len); + } + + buf_ptr+= m_base_len; + buf_len-= m_base_len; + + if (m_dir_name == nullptr) + snprintf(buf_ptr, buf_len, "%s%u", m_file_name, idx); + + else if (dir_lsn == LSN_MAX) + snprintf(buf_ptr, buf_len, "%s%c%s%u", m_dir_name, OS_PATH_SEPARATOR, + m_file_name, idx); + + else + snprintf(buf_ptr, buf_len, "%s" UINT64PF "%c%s%u", m_dir_name, dir_lsn, + OS_PATH_SEPARATOR, m_file_name, idx); +} + +void Arch_File_Ctx::build_dir_name(lsn_t dir_lsn, char *buffer, uint length) +{ + ut_ad(buffer != nullptr); + + if (m_dir_name != nullptr) + snprintf(buffer, length, "%s%c%s" UINT64PF, m_path_name, OS_PATH_SEPARATOR, + m_dir_name, dir_lsn); + else + snprintf(buffer, length, "%s", m_path_name); +} + +/** Archiver background thread */ +void Arch_Sys::archiver() +{ + my_thread_init(); + my_thread_set_name("ib_archiver"); + + Arch_File_Ctx log_file_ctx; + lsn_t log_arch_lsn= LSN_MAX; + + bool log_abort= false; + bool page_abort= false; + bool log_init= true; + + Arch_Group::init_dblwr_file_ctx( + ARCH_DBLWR_DIR, ARCH_DBLWR_FILE, ARCH_DBLWR_NUM_FILES, + static_cast(ARCH_PAGE_BLK_SIZE) * ARCH_DBLWR_FILE_CAPACITY); + + while (!page_abort || !log_abort) + { + /* Archive available redo log data. */ + bool log_wait= false; + if (!log_abort) + { + log_abort= arch_sys->log_sys()->archive(log_init, &log_file_ctx, + &log_arch_lsn, &log_wait); + log_init= false; + if (log_abort) + sql_print_information("Innodb: Exiting Log Archiver"); + } + + bool page_wait= false; + if (!page_abort) + { + /* Archive in memory data blocks to disk. */ + page_abort= arch_sys->page_sys()->archive(&page_wait); + + if (page_abort) + sql_print_information("Innodb: Exiting Page Archiver"); + } + + if (page_wait && log_wait) + /* Nothing to archive. Wait until next trigger. */ + arch_sys->archiver_wait(); + } + my_thread_end(); + arch_sys->archiver_stopped(); +} diff --git a/storage/innobase/arch/arch0log.cc b/storage/innobase/arch/arch0log.cc new file mode 100644 index 0000000000000..891c3b6194b7d --- /dev/null +++ b/storage/innobase/arch/arch0log.cc @@ -0,0 +1,934 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file arch/arch0log.cc + Innodb implementation for log archive + + *******************************************************/ + +#include "arch0log.h" +#include "clone0clone.h" +#include "log0log.h" +#include "srv0start.h" + +#include "sql_class.h" + +/** Chunk size for archiving redo log */ +const uint ARCH_LOG_CHUNK_SIZE = 1024 * 1024; + +os_offset_t Log_Arch_Client_Ctx::get_archived_file_size() const +{ + return m_group->get_file_size(); +} + +void Log_Arch_Client_Ctx::get_header_size(uint &header_sz, + uint &trailer_sz) const +{ + header_sz= log_t::START_OFFSET; + trailer_sz= OS_FILE_LOG_BLOCK_SIZE; +} + +int Log_Arch_Client_Ctx::start(byte *header, uint len) +{ + ut_ad(len >= log_t::START_OFFSET); + + auto err= arch_sys->log_sys()->start(m_group, m_begin_lsn, header, false); + + if (err != 0) + return err; + + m_state= ARCH_CLIENT_STATE_STARTED; + + ib::info() << "Clone Start LOG ARCH : start LSN : " + << m_begin_lsn; + + return 0; +} + +/** Stop redo log archiving. Exact trailer length is returned as out +parameter which could be less than the redo block size. +@param[out] trailer redo trailer. Caller must allocate buffer. +@param[in,out] len trailer length +@param[out] offset trailer block offset +@return error code */ +int Log_Arch_Client_Ctx::stop(byte *trailer, uint32_t &len, uint64_t &offset) +{ + ut_ad(m_state == ARCH_CLIENT_STATE_STARTED); + ut_ad(trailer == nullptr || len >= OS_FILE_LOG_BLOCK_SIZE); + + auto err= arch_sys->log_sys()->stop(m_group, m_end_lsn, trailer, len); + lsn_t start_lsn= m_group->get_first_lsn(); + lsn_t stop_lsn= m_group->align_lsn(m_end_lsn); + + lsn_t file_capacity= m_group->get_file_size(); + file_capacity-= log_t::START_OFFSET; + + offset= (stop_lsn - start_lsn) % file_capacity; + offset+= log_t::START_OFFSET; + + m_state= ARCH_CLIENT_STATE_STOPPED; + ib::info() << "Clone Stop LOG ARCH : end LSN : " << m_end_lsn; + + return err; +} + +/** Get archived data file details +@param[in] cbk_func callback called for each file +@param[in] ctx callback function context +@return error code */ +int Log_Arch_Client_Ctx::get_files(Log_Arch_Cbk *cbk_func, void *ctx) +{ + ut_ad(m_state == ARCH_CLIENT_STATE_STOPPED); + int err= 0; + auto size= m_group->get_file_size(); + + /* Check if the archived redo log is less than one block size. In this + case we send the data in trailer buffer. */ + auto low_begin= m_group->align_lsn(m_begin_lsn); + auto low_end= m_group->align_lsn(m_end_lsn); + + if (low_begin == low_end) + { + err= cbk_func(nullptr, size, 0, ctx); + return err; + } + + /* Get the start lsn of the group */ + auto start_lsn= m_group->get_first_lsn(); + ut_ad(m_begin_lsn >= start_lsn); + + /* Calculate first file index and offset for this client. */ + lsn_t lsn_diff= m_begin_lsn - start_lsn; + uint64_t capacity= size - log_t::START_OFFSET; + + auto idx= static_cast(lsn_diff / capacity); + uint64_t offset= lsn_diff % capacity; + + /* Set start lsn to the beginning of file. */ + start_lsn= m_begin_lsn - offset; + + offset+= log_t::START_OFFSET; + offset= ut_uint64_align_down(offset, OS_FILE_LOG_BLOCK_SIZE); + + /* Callback with all archive file names that holds the range of log + data for this client. */ + while (start_lsn < m_end_lsn) + { + char name[MAX_ARCH_LOG_FILE_NAME_LEN]; + m_group->get_file_name(idx, name, MAX_ARCH_LOG_FILE_NAME_LEN); + + idx++; + start_lsn+= capacity; + + /* For last file adjust the size based on end lsn. */ + if (start_lsn >= m_end_lsn) + { + lsn_diff= + ut_uint64_align_up(start_lsn - m_end_lsn, OS_FILE_LOG_BLOCK_SIZE); + size-= lsn_diff; + } + + err= cbk_func(name, size, offset, ctx); + + if (err != 0) + break; + offset= log_t::START_OFFSET; + } + + return err; +} + +/** Release archived data so that system can purge it */ +void Log_Arch_Client_Ctx::release() +{ + if (m_state == ARCH_CLIENT_STATE_INIT) + return; + + if (m_state == ARCH_CLIENT_STATE_STARTED) + { + uint64_t dummy_offset; + uint32_t dummy_len= 0; + + /* This is for cleanup in error cases. */ + stop(nullptr, dummy_len, dummy_offset); + } + + ut_ad(m_state == ARCH_CLIENT_STATE_STOPPED); + + arch_sys->log_sys()->release(m_group, false); + + m_group= nullptr; + + m_begin_lsn= LSN_MAX; + m_end_lsn= LSN_MAX; + + m_state= ARCH_CLIENT_STATE_INIT; +} + +os_offset_t Arch_Log_Sys::get_recommended_file_size() const +{ + if (!log_sys.is_opened() && !log_sys.is_mmap()) + { + ut_d(ut_error); + /* This shouldn't be executed, but if there was a bug, + we would prefer to return some value instead of crash, + because the archiver must not crash the server. */ + return srv_log_file_size; + } + return log_sys.file_size; +} + +void Arch_Log_Sys::update_header(byte *header, lsn_t first_lsn, + lsn_t checkpoint_lsn, lsn_t end_lsn) +{ + /* Copy Header information. */ + /* TODO: Synchronize with Key rotation or block it. */ + ut_ad(first_lsn <= checkpoint_lsn); + log_t::header_write(header, first_lsn, log_sys.is_encrypted(), true); + + /* Write checkpoint information */ + for (int i= 0; i < 2; i++) + { + auto c= header; + c+= (i == 0) ? log_t::CHECKPOINT_1 : log_t::CHECKPOINT_2; + mach_write_to_8(c, checkpoint_lsn); + mach_write_to_8(c + 8, end_lsn); + mach_write_to_4(c + 60, my_crc32c(0, c, 60)); + } +} + +/** Start redo log archiving. +If archiving is already in progress, the client +is attached to current group. +@param[out] group log archive group +@param[out] start_lsn start lsn for client +@param[out] header redo log header +@param[in] is_durable if client needs durable archiving +@return error code */ +int Arch_Log_Sys::start(Arch_Group *&group, lsn_t &start_lsn, byte *header, + bool is_durable) +{ + bool create_new_group= false; + + memset(header, 0, log_t::START_OFFSET); + log_make_checkpoint(); + + arch_mutex_enter(); + + if (m_state == ARCH_STATE_READ_ONLY) + { + arch_mutex_exit(); + return 0; + } + + /* Wait for idle state, if preparing to idle. */ + if (!wait_idle()) + { + int err= 0; + + if (srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP) + { + err= ER_QUERY_INTERRUPTED; + my_error(err, MYF(0)); + } + else + { + err= ER_INTERNAL_ERROR; + my_error(err, MYF(0), "Log Archiver wait too long"); + } + + arch_mutex_exit(); + return err; + } + + ut_ad(m_state != ARCH_STATE_PREPARE_IDLE); + + if (m_state == ARCH_STATE_ABORT) + { + arch_mutex_exit(); + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + + /* Start archiver task, if needed. */ + if (m_state == ARCH_STATE_INIT) + { + auto err= arch_sys->start_archiver(); + + if (err != 0) + { + arch_mutex_exit(); + sql_print_error("Could not start Log Archiver background task"); + return err; + } + } + + /* Start archiving from checkpoint LSN. */ + log_sys.latch.wr_lock(SRW_LOCK_CALL); + + start_lsn= log_sys.last_checkpoint_lsn; + lsn_t checkpoint_end_lsn= log_sys.last_checkpoint_end_lsn; + + auto first_lsn= Arch_Group::align_lsn(start_lsn, log_sys.get_first_lsn()); + const auto start_index= 0; + const auto start_offset= log_sys.calc_lsn_offset(first_lsn); + + /* Need to create a new group if archiving is not in progress. */ + if (m_state == ARCH_STATE_IDLE || m_state == ARCH_STATE_INIT) + { + m_archived_lsn.store(first_lsn); + create_new_group= true; + } + + /* Set archiver state to active. */ + if (m_state != ARCH_STATE_ACTIVE) + { + m_state= ARCH_STATE_ACTIVE; + arch_sys->signal_archiver(); + } + + log_sys.latch.wr_unlock(); + + /* Create a new group. */ + if (create_new_group) + { + m_current_group = UT_NEW( + Arch_Group(first_lsn, start_lsn, log_t::START_OFFSET, &m_mutex), + mem_key_archive); + if (!m_current_group) + { + arch_mutex_exit(); + + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(Arch_Group)); + return ER_OUTOFMEMORY; + } + + os_offset_t file_size = get_recommended_file_size(); + DBUG_EXECUTE_IF("clone_arch_log_stop_file_end", + file_size = 4 * 1024 * 1024;); + auto db_err= + m_current_group->init_file_ctx(ARCH_DIR, ARCH_LOG_DIR, ARCH_LOG_FILE, 0, + file_size, 0); + + if (db_err != DB_SUCCESS) + { + arch_mutex_exit(); + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(Arch_File_Ctx)); + return ER_OUTOFMEMORY; + } + + m_start_log_index= start_index; + m_start_log_offset= start_offset; + + m_chunk_size= ARCH_LOG_CHUNK_SIZE; + + m_group_list.push_back(m_current_group); + } + + /* Attach to the current group. */ + m_current_group->attach(is_durable); + + group= m_current_group; + + arch_mutex_exit(); + + /* Update header with checkpoint LSN. Note, that arch mutex is released + and m_current_group should no longer be accessed. The group cannot be + freed as we have already attached to it. */ + update_header(header, first_lsn, start_lsn, checkpoint_end_lsn); + + return 0; +} + +#ifdef UNIV_DEBUG +void Arch_Group::adjust_end_lsn(lsn_t &stop_lsn, uint32_t &blk_len) +{ + stop_lsn= get_first_lsn(); + stop_lsn+= get_file_size() - log_t::START_OFFSET; + blk_len= 0; + + /* Increase Stop LSN 64 bytes ahead of file end not exceeding + redo block size. */ + DBUG_EXECUTE_IF("clone_arch_log_extra_bytes", + blk_len= OS_FILE_LOG_BLOCK_SIZE; + stop_lsn+= 64; + stop_lsn= std::min(stop_lsn, log_sys.get_lsn_approx());); +} + +void Arch_Group::adjust_copy_length(lsn_t arch_lsn, uint32_t ©_len) +{ + lsn_t end_lsn= LSN_MAX; + uint32_t blk_len= 0; + adjust_end_lsn(end_lsn, blk_len); + + if (end_lsn <= arch_lsn) + { + copy_len= 0; + return; + } + + /* Adjust if copying beyond end LSN. */ + auto len_left= end_lsn - arch_lsn; + len_left= ut_uint64_align_down(len_left, OS_FILE_LOG_BLOCK_SIZE); + + if (len_left < copy_len) + copy_len= static_cast(len_left); +} + +#endif /* UNIV_DEBUG */ + +/** Stop redo log archiving. +If other clients are there, the client is detached from +the current group. +@param[out] group log archive group +@param[out] stop_lsn stop lsn for client +@param[out] log_blk redo log trailer block +@param[in,out] blk_len length in bytes +@return error code */ +int Arch_Log_Sys::stop(Arch_Group *group, lsn_t &stop_lsn, byte *log_blk, + uint32_t &blk_len) +{ + int err= 0; + stop_lsn= m_archived_lsn.load(); + + if (log_blk != nullptr) + { + /* Get the current LSN and trailer block. */ + /* TODO: Block concurrent log file resize. */ + log_sys.get_last_block(stop_lsn, log_blk, blk_len); + + DBUG_EXECUTE_IF("clone_arch_log_stop_file_end", + group->adjust_end_lsn(stop_lsn, blk_len);); + + /* Will throw error, if shutdown. We still continue + with detach but return the error. */ + err= wait_archive_complete(group->align_lsn(stop_lsn)); + } + + arch_mutex_enter(); + + if (m_state == ARCH_STATE_READ_ONLY) + { + arch_mutex_exit(); + return 0; + } + + auto count_active_client= group->detach(stop_lsn, nullptr); + ut_ad(group->is_referenced()); + + if (!group->is_active() && err == 0) + { + /* Archiving for the group has already stopped. */ + my_error(ER_INTERNAL_ERROR, MYF(0), "Clone: Log Archiver failed"); + err= ER_INTERNAL_ERROR; + } + + if (group->is_active() && count_active_client == 0) + { + /* No other active client. Prepare to get idle. */ + if (m_state == ARCH_STATE_ACTIVE) + { + /* The active group must be the current group. */ + ut_ad(group == m_current_group); + m_state= ARCH_STATE_PREPARE_IDLE; + arch_sys->signal_archiver(); + } + } + arch_mutex_exit(); + return err; +} + +void Arch_Log_Sys::force_abort() +{ + lsn_t lsn_max= LSN_MAX; /* unused */ + uint to_archive= 0; /* unused */ + check_set_state(true, &lsn_max, &to_archive); + /* Above line changes state to ARCH_STATE_PREPARE_IDLE or ARCH_STATE_ABORT. + Let us notify the background thread to give it chance to notice the change and + wait for it to transition to ARCH_STATE_IDLE before returning (in case of + ARCH_STATE_ABORT, wait_idle() does nothing).*/ + arch_mutex_enter(); + wait_idle(); + arch_mutex_exit(); +} + +void Arch_Log_Sys::update_state(Arch_State state) +{ + mysql_mutex_assert_owner(&m_mutex); + log_sys.latch.rd_lock(SRW_LOCK_CALL); + m_state= state; + log_sys.latch.rd_unlock(); +} + +void Arch_Log_Sys::wait_archiver(lsn_t next_write_lsn) +{ + ut_ad(log_sys.latch_have_wr()); + if (!is_active()) + return; + + lsn_t archiver_lsn= get_archived_lsn(); + lsn_t limit_lsn= log_sys.log_capacity + archiver_lsn; + if (limit_lsn >= next_write_lsn) + return; + + log_sys.latch.wr_unlock(); + + /* Sleep for 10 millisecond. */ + Clone_Msec sleep_time(10); + /* Generate alert message every 1 second. */ + Clone_Sec alert_interval(1); + /* Wait for 5 second for archiver to catch up then abort archiver. */ + Clone_Sec time_out(5); + + auto check_fn= [&](bool alert, bool &result) + { + mysql_mutex_assert_owner(&m_mutex); + if (srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP) + return ER_QUERY_INTERRUPTED; + + lsn_t archiver_lsn= get_archived_lsn(); + lsn_t limit_lsn= log_sys.log_capacity + archiver_lsn; + + result= limit_lsn < next_write_lsn; + if (result && alert) + sql_print_information("Innodb: Log writer waiting for archiver." + " Next LSN to write: %" PRIu64 ", Archiver LSN: %" PRIu64 ".", + next_write_lsn, archiver_lsn); + return 0; + }; + + /* Need to wait for archiver to catch up. */ + arch_sys->signal_archiver(); + + bool is_timeout= false; + arch_mutex_enter(); + auto err= Clone_Sys::wait(sleep_time, time_out, alert_interval, check_fn, + &m_mutex, is_timeout); + arch_mutex_exit(); + + if (err == 0 && is_timeout) + { + force_abort(); + sql_print_error("Innodb: Log writer waited too long for archiver" + " (5 seconds). Next LSN to write: %" PRIu64 ", Archiver LSN: %" PRIu64 + ". Aborted redo-archiver task. Consider increasing innodb_redo_log_size.", + next_write_lsn, archiver_lsn); + } + log_sys.latch.wr_lock(SRW_LOCK_CALL); +} + +/** Release the current group from client. +@param[in] group group the client is attached to +@param[in] is_durable if client needs durable archiving */ +void Arch_Log_Sys::release(Arch_Group *group, bool is_durable) +{ + arch_mutex_enter(); + + group->release(is_durable); + + /* Check if there are other references or archiving is still + in progress. */ + if (group->is_referenced() || group->is_active()) + { + arch_mutex_exit(); + return; + } + /* Cleanup the group. */ + ut_ad(group != m_current_group); + + m_group_list.remove(group); + UT_DELETE(group); + arch_mutex_exit(); +} + +/** Check and set log archive system state and output the +amount of redo log available for archiving. +@param[in] is_abort need to abort +@param[in,out] archived_lsn LSN up to which redo log is archived +@param[out] to_archive amount of redo log to be archived */ +Arch_State Arch_Log_Sys::check_set_state(bool is_abort, lsn_t *archived_lsn, + uint *to_archive) +{ + auto is_shutdown= (srv_shutdown_state.load() == SRV_SHUTDOWN_LAST_PHASE || + srv_shutdown_state.load() == SRV_SHUTDOWN_EXIT_THREADS); + + auto need_to_abort= (is_abort || is_shutdown); + *to_archive= 0; + lsn_t last_write_lsn= 0; + arch_mutex_enter(); + + switch (m_state) { + case ARCH_STATE_ACTIVE: + + if (*archived_lsn != LSN_MAX) + { + /* Update system archived LSN from input */ + ut_ad(*archived_lsn >= m_archived_lsn.load()); + m_archived_lsn.store(*archived_lsn); + } + else + { + /* If input is not initialized, + set from system archived LSN */ + *archived_lsn= m_archived_lsn.load(); + } + + lsn_t lsn_diff; + + last_write_lsn= log_sys.is_mmap() + ? log_sys.get_flushed_lsn() + : log_sys.write_lsn.load(); + /* Check redo log data ready to archive. */ + ut_ad(last_write_lsn >= m_archived_lsn.load()); + + lsn_diff= last_write_lsn - m_archived_lsn.load(); + + lsn_diff= ut_uint64_align_down(lsn_diff, OS_FILE_LOG_BLOCK_SIZE); + + /* Adjust archive data length if bigger than chunks size. */ + if (lsn_diff < m_chunk_size) + *to_archive = static_cast(lsn_diff); + else + *to_archive = m_chunk_size; + + if (!need_to_abort) + break; + + if (!is_shutdown) + { + ut_ad(is_abort); + /* If caller asked to abort, move to prepare idle state. Archiver + thread will move to IDLE state eventually. */ + update_state(ARCH_STATE_PREPARE_IDLE); + break; + } + [[fallthrough]]; + + case ARCH_STATE_PREPARE_IDLE: + { + /* No active clients. Mark the group inactive and move + to idle state. */ + m_current_group->disable(m_archived_lsn.load()); + + /* If no client reference, free the group. */ + if (!m_current_group->is_referenced()) { + m_group_list.remove(m_current_group); + + UT_DELETE(m_current_group); + } + + m_current_group= nullptr; + update_state(ARCH_STATE_IDLE); + } + [[fallthrough]]; + + case ARCH_STATE_IDLE: + case ARCH_STATE_INIT: + + /* Abort archiver thread only in case of shutdown. */ + if (is_shutdown) + update_state(ARCH_STATE_ABORT); + break; + + case ARCH_STATE_ABORT: + /* We could abort archiver from log_writer when + it is already in the aborted state (shutdown). */ + break; + + default: + ut_d(ut_error); + } + + auto ret_state= m_state; + arch_mutex_exit(); + + return ret_state; +} + +dberr_t Arch_Log_Sys::copy_log(Arch_File_Ctx *file_ctx, uint length) +{ + dberr_t err= DB_SUCCESS; + + if (file_ctx->is_closed()) + { + /* Open system redo log file context */ + err= file_ctx->open(true, LSN_MAX, m_start_log_index, m_start_log_offset, + get_recommended_file_size()); + if (err != DB_SUCCESS) + return err; + } + + uint write_size= 0; + Arch_Group *curr_group = arch_sys->log_sys()->get_arch_group(); + + /* Copy log data into one or more files in archiver group. */ + while (length > 0) + { + auto len_copy = static_cast(length); + auto len_left = file_ctx->bytes_left(); + + /* Current file is over, switch to next file. */ + if (len_left == 0) + { + err= file_ctx->open_next(LSN_MAX, log_t::START_OFFSET, + get_recommended_file_size()); + if (err != DB_SUCCESS) + return (err); + + len_left= file_ctx->bytes_left(); + ut_ad(len_left > 0); + } + + if (len_left == 0) + return DB_ERROR; + + /* Write as much as possible from current file. */ + write_size= len_left < len_copy ? static_cast(len_left) : length; + + err = curr_group->write_to_file(file_ctx, nullptr, write_size, false, false); + if (err != DB_SUCCESS) + return (err); + + ut_ad(length >= write_size); + length-= write_size; + } + return DB_SUCCESS; +} + +bool Arch_Log_Sys::wait_idle() { + mysql_mutex_assert_owner(&m_mutex); + + if (m_state == ARCH_STATE_PREPARE_IDLE) { + arch_sys->signal_archiver(); + bool is_timeout= false; + int alert_count= 0; + auto thd= current_thd; + + auto err= Clone_Sys::wait_default( + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_mutex); + result= (m_state == ARCH_STATE_PREPARE_IDLE); + + if (srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP || + (thd && thd_killed(thd))) + { + if (thd) my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + + if (result) + { + arch_sys->signal_archiver(); + /* Print messages every 1 minute - default is 5 seconds. */ + if (alert && ++alert_count == 12) + { + alert_count= 0; + ib::info() << "Log Archiving start: waiting for idle state"; + } + } + return 0; + }, + &m_mutex, is_timeout); + + if (err == 0 && is_timeout) + { + err = ER_INTERNAL_ERROR; + ib::info() << "Log Archiving start: wait for idle state timed out"; + ut_d(ut_error); + } + + if (err != 0) + return false; + } + return true; +} + +/** Wait for redo log archive up to the target LSN. +We need to wait till current log sys LSN during archive stop. +@param[in] target_lsn target archive LSN to wait for +@return error code */ +int Arch_Log_Sys::wait_archive_complete(lsn_t target_lsn) +{ + /* Check and wait for archiver thread if needed. */ + if (m_archived_lsn.load() < target_lsn) + { + arch_sys->signal_archiver(); + + bool is_timeout= false; + int alert_count= 0; + auto thd= current_thd; + + auto err= Clone_Sys::wait_default( + [&](bool alert, bool &result) + { + /* Read consistent state. */ + arch_mutex_enter(); + auto state= m_state; + arch_mutex_exit(); + + /* Check if we need to abort. */ + if (state == ARCH_STATE_ABORT || + srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP || + (thd && thd_killed(thd))) + { + if (thd) my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + + if (state == ARCH_STATE_IDLE || state == ARCH_STATE_PREPARE_IDLE) + { + my_error(ER_INTERNAL_ERROR, MYF(0), "Clone: Log Archiver failed"); + return ER_INTERNAL_ERROR; + } + + ut_ad(state == ARCH_STATE_ACTIVE); + + /* Check if archived LSN is behind target. */ + auto archived_lsn= m_archived_lsn.load(); + result= (archived_lsn < target_lsn); + + lsn_t last_write_lsn= log_sys.is_mmap() + ? log_sys.get_flushed_lsn() + : log_sys.write_lsn.load(); + /* Trigger flush if needed */ + auto flush= last_write_lsn < target_lsn; + + if (result) + { + /* More data needs to be archived. */ + arch_sys->signal_archiver(); + + /* Write system redo log if needed. */ + if (flush) + log_write_up_to(target_lsn, false); + + /* Print messages every 1 minute - default is 5 seconds. */ + if (alert && ++alert_count == 12) + { + alert_count= 0; + ib::info() + << "Clone Log archive stop: waiting for archiver to " + "finish archiving log till LSN: " + << target_lsn << " Archived LSN: " << archived_lsn; + } + } + return 0; + }, + nullptr, is_timeout); + + if (err == 0 && is_timeout) + { + ib::info() << "Clone Log archive stop: wait for Archiver timed out"; + + err= ER_INTERNAL_ERROR; + my_error(ER_INTERNAL_ERROR, MYF(0), "Clone: Log Archiver wait too long"); + ut_d(ut_error); + } + return err; + } + return 0; +} + +/** Archive accumulated redo log in current group. +This interface is for archiver background task to archive redo log +data by calling it repeatedly over time. +@param[in, out] init true when called the first time; it will then + be set to false +@param[in] curr_ctx system redo logs to copy data from +@param[out] arch_lsn LSN up to which archiving is completed +@param[out] wait true, if no more redo to archive +@return true, if archiving is aborted */ +bool Arch_Log_Sys::archive(bool init, Arch_File_Ctx *curr_ctx, lsn_t *arch_lsn, + bool *wait) +{ + dberr_t err= DB_SUCCESS; + bool is_abort= false; + + /* Initialize system redo log file context first time. */ + if (init) + { + /* We will use curr_ctx to read data from existing log file.*/ + err= curr_ctx->init(srv_log_group_home_dir, nullptr, + LOG_FILE_NAME_PREFIX, 1); + if (err != DB_SUCCESS) + is_abort= true; + } + + /* Find archive system state and amount of log data to archive. */ + uint32_t arch_len= 0; + auto curr_state = check_set_state(is_abort, arch_lsn, &arch_len); + + if (curr_state == ARCH_STATE_ACTIVE) + { + /* Adjust archiver length to no go beyond file end. */ + DBUG_EXECUTE_IF("clone_arch_log_stop_file_end", + m_current_group->adjust_copy_length(*arch_lsn, arch_len);); + + /* Simulate archive error. */ + DBUG_EXECUTE_IF("clone_redo_no_archive", arch_len = 0;); + + if (arch_len == 0) + { + /* Nothing to archive. Need to wait. */ + *wait = true; + return false; + } + + /* Copy data from system redo log files to archiver files */ + err= copy_log(curr_ctx, arch_len); + + /* Simulate archive error. */ + DBUG_EXECUTE_IF("clone_redo_archive_error", err = DB_ERROR;); + + if (err == DB_SUCCESS) + { + *arch_lsn+= arch_len; + *wait= false; + return false; + } + + /* Force abort in case of an error archiving data. */ + curr_state= check_set_state(true, arch_lsn, &arch_len); + } + + if (curr_state == ARCH_STATE_ABORT) { + curr_ctx->close(); + return true; + } + + if (curr_state == ARCH_STATE_IDLE || curr_state == ARCH_STATE_INIT) + { + curr_ctx->close(); + *arch_lsn= LSN_MAX; + *wait= true; + return false; + } + + ut_ad(curr_state == ARCH_STATE_PREPARE_IDLE); + *wait= false; + return false; +} diff --git a/storage/innobase/arch/arch0page.cc b/storage/innobase/arch/arch0page.cc new file mode 100644 index 0000000000000..bded4242ca662 --- /dev/null +++ b/storage/innobase/arch/arch0page.cc @@ -0,0 +1,3070 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file arch/arch0page.cc + Innodb implementation for page archive + + *******************************************************/ + +#include "arch0page.h" +#include "arch0recv.h" +#include "clone0clone.h" +#include "log0log.h" +#include "srv0start.h" +#include "srv0mon.h" + +#include "log.h" +#include "sql_class.h" + +#ifdef UNIV_DEBUG +/** Archived page file default size in number of blocks. */ +uint ARCH_PAGE_FILE_CAPACITY= + (ARCH_PAGE_BLK_SIZE - ARCH_PAGE_BLK_HEADER_LENGTH) / ARCH_BLK_PAGE_ID_SIZE; + +/** Archived page data file size (without header) in number of blocks. */ +uint ARCH_PAGE_FILE_DATA_CAPACITY= + ARCH_PAGE_FILE_CAPACITY - ARCH_PAGE_FILE_NUM_RESET_PAGE; +#endif + +void Arch_Reset_File::init() +{ + m_file_index= 0; + m_lsn= LSN_MAX; + m_start_point.clear(); +} + +Arch_File_Ctx Arch_Group::s_dblwr_file_ctx; + +Arch_Group::~Arch_Group() +{ + ut_ad(!m_is_active); + + m_file_ctx.close(); + + if (m_active_file.m_file != OS_FILE_CLOSED) + os_file_close(m_active_file); + + if (m_durable_file.m_file != OS_FILE_CLOSED) + os_file_close(m_durable_file); + + if (m_active_file_name != nullptr) + ut_free(m_active_file_name); + + if (m_durable_file_name != nullptr) + ut_free(m_durable_file_name); + + if (!is_durable()) + m_file_ctx.delete_files(m_begin_lsn); +} + +dberr_t Arch_Group::write_to_doublewrite_file(Arch_File_Ctx *from_file, + byte *from_buffer, + uint write_size, + Arch_Page_Dblwr_Offset offset) +{ + dberr_t err= DB_SUCCESS; + + ut_ad(!s_dblwr_file_ctx.is_closed()); + + switch (offset) + { + case ARCH_PAGE_DBLWR_RESET_PAGE: + DBUG_EXECUTE_IF("crash_before_reset_block_dblwr_flush", DBUG_SUICIDE();); + break; + + case ARCH_PAGE_DBLWR_PARTIAL_FLUSH_PAGE: + DBUG_EXECUTE_IF("crash_before_partial_block_dblwr_flush", + DBUG_SUICIDE();); + break; + + case ARCH_PAGE_DBLWR_FULL_FLUSH_PAGE: + DBUG_EXECUTE_IF("crash_before_full_block_dblwr_flush", DBUG_SUICIDE();); + break; + } + + err= s_dblwr_file_ctx.write(from_file, from_buffer, + offset * ARCH_PAGE_BLK_SIZE, write_size); + if (err != DB_SUCCESS) + return (err); + + s_dblwr_file_ctx.flush(); + + return err; +} + +dberr_t Arch_Group::init_dblwr_file_ctx(const char *path, const char *base_file, + uint num_files, uint64_t file_size) +{ + auto err= s_dblwr_file_ctx.init(ARCH_DIR, path, base_file, num_files); + + if (err != DB_SUCCESS) + { + ut_ad(s_dblwr_file_ctx.get_phy_size() == file_size); + return (err); + } + + err= s_dblwr_file_ctx.open(false, LSN_MAX, 0, 0, 0); + + if (err != DB_SUCCESS) + return (err); + + return s_dblwr_file_ctx.resize_and_overwrite_with_zeros(file_size); +} + +dberr_t Arch_Group::build_active_file_name() +{ + char dir_name[MAX_ARCH_DIR_NAME_LEN]; + auto length = MAX_ARCH_DIR_NAME_LEN + 1 + MAX_ARCH_PAGE_FILE_NAME_LEN + 1; + + if (m_active_file_name != nullptr) + return DB_SUCCESS; + + m_active_file_name= static_cast(ut_malloc(length, mem_key_archive)); + + if (m_active_file_name == nullptr) + return DB_OUT_OF_MEMORY; + + get_dir_name(dir_name, MAX_ARCH_DIR_NAME_LEN); + + snprintf(m_active_file_name, length, "%s%c%s", dir_name, OS_PATH_SEPARATOR, + ARCH_PAGE_GROUP_ACTIVE_FILE_NAME); + + return DB_SUCCESS; +} + +dberr_t Arch_Group::build_durable_file_name() +{ + char dir_name[MAX_ARCH_DIR_NAME_LEN]; + auto length = MAX_ARCH_DIR_NAME_LEN + 1 + MAX_ARCH_PAGE_FILE_NAME_LEN + 1; + + if (m_durable_file_name != nullptr) + return DB_SUCCESS; + + m_durable_file_name= static_cast(ut_malloc(length, mem_key_archive)); + + if (m_durable_file_name == nullptr) + return DB_OUT_OF_MEMORY; + + get_dir_name(dir_name, MAX_ARCH_DIR_NAME_LEN); + + snprintf(m_durable_file_name, length, "%s%c%s", dir_name, OS_PATH_SEPARATOR, + ARCH_PAGE_GROUP_DURABLE_FILE_NAME); + + return DB_SUCCESS; +} + +int Arch_Group::mark_active() +{ + dberr_t db_err= build_active_file_name(); + + if (db_err != DB_SUCCESS) + return ER_OUTOFMEMORY; + + os_file_create_t option; + os_file_type_t type; + + bool success; + bool exists; + + success= os_file_status(m_active_file_name, &exists, &type); + + if (!success) + return ER_CANT_OPEN_FILE; + + ut_ad(!exists); + option= OS_FILE_CREATE; + + ut_ad(m_active_file.m_file == OS_FILE_CLOSED); + + /* In case of a failure, we would use the error from os_file_create. */ + std::ignore= os_file_create_subdirs_if_needed(m_active_file_name); + + m_active_file= os_file_create(innodb_arch_file_key, m_active_file_name, + option, OS_CLONE_LOG_FILE, false, &success); + + int err= (success ? 0 : ER_CANT_OPEN_FILE); + return err; +} + +int Arch_Group::mark_durable() +{ + dberr_t db_err= build_durable_file_name(); + + if (db_err != DB_SUCCESS) + return ER_OUTOFMEMORY; + + os_file_create_t option; + os_file_type_t type; + + bool success; + bool exists; + + success= os_file_status(m_durable_file_name, &exists, &type); + + if (exists) + return 0; + + if (!success) + return ER_CANT_OPEN_FILE; + + option= OS_FILE_CREATE; + + /* In case of a failure, we would use the error from os_file_create. */ + std::ignore= os_file_create_subdirs_if_needed(m_durable_file_name); + + ut_ad(m_durable_file.m_file == OS_FILE_CLOSED); + + m_durable_file= os_file_create(innodb_arch_file_key, m_durable_file_name, + option, OS_CLONE_LOG_FILE, false, &success); + + int err= (success ? 0 : ER_CANT_OPEN_FILE); + return err; +} + +int Arch_Group::mark_inactive() +{ + os_file_type_t type; + + bool success; + bool exists; + + dberr_t db_err; + + db_err= build_active_file_name(); + + if (db_err != DB_SUCCESS) + return ER_OUTOFMEMORY; + + success= os_file_status(m_active_file_name, &exists, &type); + + if (!success) + return ER_CANT_OPEN_FILE; + + if (!exists) + return 0; + + if (m_active_file.m_file != OS_FILE_CLOSED) + { + os_file_close(m_active_file); + m_active_file.m_file = OS_FILE_CLOSED; + } + + success= os_file_delete(innodb_arch_file_key, m_active_file_name); + + int err= (success ? 0 : ER_CANT_OPEN_FILE); + return err; +} + +dberr_t Arch_Group::write_file_header(byte *from_buffer, uint length) +{ + dberr_t err; + + ut_ad(!m_file_ctx.is_closed()); + + /* Write to the doublewrite buffer before writing to the actual file */ + Arch_Group::write_to_doublewrite_file(nullptr, from_buffer, length, + ARCH_PAGE_DBLWR_RESET_PAGE); + + DBUG_EXECUTE_IF("crash_after_reset_block_dblwr_flush", DBUG_SUICIDE();); + + err= m_file_ctx.write(nullptr, from_buffer, 0, length); + + if (err == DB_SUCCESS) + /* Flush the file to make sure the changes are made persistent as there + would be no way to recover the data otherwise in case of a crash. */ + m_file_ctx.flush(); + + return err; +} + +dberr_t Arch_Group::open_file(Arch_Page_Pos write_pos, bool create_new) +{ + dberr_t err; + + ut_d(auto count = get_file_count()); + ut_ad(count > 0); + + ut_a(m_file_size == ARCH_PAGE_BLK_SIZE * ARCH_PAGE_FILE_CAPACITY); + + if (!create_new) + { + auto block_num= write_pos.m_block_num; + uint file_index= Arch_Block::get_file_index(block_num, ARCH_DATA_BLOCK); + auto offset= static_cast( + Arch_Block::get_file_offset(block_num, ARCH_DATA_BLOCK)); + + ut_ad(file_index + 1 == count); + + err= m_file_ctx.open(false, m_begin_lsn, file_index, offset, m_file_size); + } + else + err= m_file_ctx.open_new(m_begin_lsn, m_file_size, m_header_len); + + return err; +} + +void Arch_File_Ctx::update_stop_point(uint file_index, lsn_t stop_lsn) +{ + auto last_point_index= m_stop_points.size() - 1; + + if (!m_stop_points.size() || last_point_index != file_index) + m_stop_points.push_back(stop_lsn); + else + m_stop_points[last_point_index] = stop_lsn; +} + +void Arch_File_Ctx::save_reset_point_in_mem(lsn_t lsn, Arch_Page_Pos pos) +{ + uint current_file_index= + Arch_Block::get_file_index(pos.m_block_num, ARCH_DATA_BLOCK); + + Arch_Point reset_point; + reset_point.lsn= lsn; + reset_point.pos= pos; + + Arch_Reset_File reset_file; + + if (m_reset.size()) + { + reset_file= m_reset.back(); + + if (reset_file.m_file_index == current_file_index) + { + reset_file.m_start_point.push_back(reset_point); + m_reset[m_reset.size() - 1]= reset_file; + return; + } + } + /* Reset info maintained in a new file. */ + reset_file.init(); + reset_file.m_file_index= current_file_index; + reset_file.m_lsn= lsn; + reset_file.m_start_point.push_back(reset_point); + m_reset.push_back(reset_file); +} + +bool Arch_File_Ctx::find_reset_point(lsn_t check_lsn, Arch_Point &reset_point) +{ + if (!m_reset.size()) + return false; + + Arch_Reset_File file_reset_compare; + file_reset_compare.m_lsn= check_lsn; + + /* Finds the file which has the element that is >= to check_lsn */ + auto reset_it = std::lower_bound( + m_reset.begin(), m_reset.end(), file_reset_compare, + [](const Arch_Reset_File &lhs, const Arch_Reset_File &rhs) + { + return (lhs.m_lsn < rhs.m_lsn); + }); + + if (reset_it != m_reset.end() && reset_it->m_lsn == check_lsn) + { + reset_point= reset_it->m_start_point.front(); + return true; + } + + if (reset_it == m_reset.begin()) + return false; + + /* The element that is less than check_lsn, which we're interested in, + will be in the previous position. */ + --reset_it; + ut_ad(reset_it->m_lsn < check_lsn); + + auto reset_file= *reset_it; + auto reset_start_point= reset_file.m_start_point; + + Arch_Point reset_point_compare; + reset_point_compare.lsn= check_lsn; + + /* Find the first start point whose lsn is >= to check_lsn. */ + auto reset_point_it= std::lower_bound( + reset_start_point.begin(), reset_start_point.end(), reset_point_compare, + [](const Arch_Point &lhs, const Arch_Point &rhs) + { + return (lhs.lsn < rhs.lsn); + }); + + if (reset_point_it == reset_start_point.end() || + reset_point_it->lsn != check_lsn) + { + ut_ad(reset_point_it != reset_start_point.begin()); + --reset_point_it; + } + + reset_point= *reset_point_it; + return true; +} + +dberr_t Arch_File_Ctx::write(Arch_File_Ctx *from_file, byte *from_buffer, + uint offset, uint size) +{ + dberr_t err; + + ut_ad(offset + size <= m_size); + ut_ad(!is_closed()); + + if (from_buffer == nullptr) + { + ut_ad(offset + size <= from_file->get_size()); + ut_ad(!from_file->is_closed()); + + err= os_file_copy(from_file->m_file, offset, m_file, offset, size); + } else + err= os_file_write(IORequestWrite, "Page Track File", m_file, from_buffer, + offset, size); + return err; +} + +bool Arch_File_Ctx::find_stop_point(Arch_Group *group, lsn_t check_lsn, + Arch_Point &stop_point, + Arch_Page_Pos last_pos) +{ + stop_point.lsn= LSN_MAX; + stop_point.pos.init(); + auto arch_page_sys= arch_sys->page_sys(); + + arch_page_sys->arch_oper_mutex_enter(); + + if (!m_stop_points.size()) + { + arch_page_sys->arch_oper_mutex_exit(); + return false; + } + ut_ad(m_stop_points.back() <= arch_page_sys->get_latest_stop_lsn()); + + /* 1. Find the file where the block we need to stop at is present */ + uint file_index= 0; + + for (uint i = 0; i < m_stop_points.size(); ++i) + { + file_index= i; + + if (m_stop_points[i] >= check_lsn) + break; + } + ut_ad((m_stop_points[file_index] >= check_lsn && + (file_index == 0 || m_stop_points[file_index - 1] < check_lsn))); + + arch_page_sys->arch_oper_mutex_exit(); + + /* 2. Find the block in the file where to stop. */ + byte header_buf[ARCH_PAGE_BLK_HEADER_LENGTH]; + + Arch_Page_Pos left_pos; + left_pos.m_block_num= ARCH_PAGE_FILE_DATA_CAPACITY * file_index; + + Arch_Page_Pos right_pos; + + if (file_index < m_stop_points.size() - 1) + right_pos.m_block_num= + left_pos.m_block_num + ARCH_PAGE_FILE_DATA_CAPACITY - 1; + else + right_pos.m_block_num = last_pos.m_block_num; + + lsn_t block_stop_lsn; + int err; + + while (left_pos.m_block_num <= right_pos.m_block_num) + { + Arch_Page_Pos middle_pos; + middle_pos.init(); + middle_pos.m_offset = 0; + + middle_pos.m_block_num= left_pos.m_block_num + + (right_pos.m_block_num - left_pos.m_block_num) / 2; + + /* Read the block header for data length and stop lsn info. */ + err= group->read_data(middle_pos, header_buf, ARCH_PAGE_BLK_HEADER_LENGTH); + + if (err != 0) + return false; + + block_stop_lsn= Arch_Block::get_stop_lsn(header_buf); + auto data_len= Arch_Block::get_data_len(header_buf); + + middle_pos.m_offset= data_len + ARCH_PAGE_BLK_HEADER_LENGTH; + + if (block_stop_lsn >= check_lsn) + { + stop_point.lsn= block_stop_lsn; + stop_point.pos= middle_pos; + } + + if (left_pos.m_block_num == right_pos.m_block_num || + block_stop_lsn == check_lsn) + break; + + if (block_stop_lsn > check_lsn) + right_pos.m_block_num= middle_pos.m_block_num - 1; + else + left_pos.m_block_num= middle_pos.m_block_num + 1; + } + + ut_ad(stop_point.lsn != LSN_MAX); + return true; +} + +#ifdef UNIV_DEBUG + +bool Arch_File_Ctx::validate_stop_point_in_file(Arch_Group *group, + pfs_os_file_t file, + uint file_index) +{ + lsn_t stop_lsn= LSN_MAX; + bool last_file= file_index + 1 == m_count; + + if (last_file && group->is_active() && group->get_end_lsn() == LSN_MAX) + /* Just return true if this is the case as the block might not have been + flushed to disk yet */ + return true; + + if (file_index >= m_stop_points.size()) + ut_error; + + /* Read from file to the user buffer. */ + uint64_t offset; + + if (!last_file) + offset= ARCH_PAGE_FILE_DATA_CAPACITY * ARCH_PAGE_BLK_SIZE; + else + offset= Arch_Block::get_file_offset(group->get_stop_pos().m_block_num, + ARCH_DATA_BLOCK); + auto buf= std::make_unique(ARCH_PAGE_BLK_SIZE); + + /* Read the entire reset block. */ + dberr_t err= + os_file_read(IORequestRead, file, buf.get(), offset, ARCH_PAGE_BLK_SIZE, + nullptr); + + if (err != DB_SUCCESS) + return false; + + stop_lsn = Arch_Block::get_stop_lsn(buf.get()); + + if (stop_lsn != m_stop_points[file_index]) + ut_error; + + DBUG_PRINT("page_archiver", ("File stop point: %" PRIu64 "", stop_lsn)); + return true; +} + +bool Arch_File_Ctx::validate_reset_block_in_file(pfs_os_file_t file, + uint file_index, + uint &reset_count) +{ + /* Read from file to the user buffer. */ + auto buf= std::make_unique(ARCH_PAGE_BLK_SIZE); + + /* Read the entire reset block. */ + dberr_t err= + os_file_read(IORequestRead, file, buf.get(), 0, ARCH_PAGE_BLK_SIZE, + nullptr); + if (err != DB_SUCCESS) + return false; + + auto data_length= Arch_Block::get_data_len(buf.get()); + + if (data_length == 0) + /* No reset, move to the next file. */ + return true; + + ut_ad(data_length >= ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE + + ARCH_PAGE_FILE_HEADER_RESET_POS_SIZE); + + Arch_Reset_File reset_file; + + if (!m_reset.size() || reset_count >= m_reset.size()) + ut_error; + + reset_file= m_reset.at(reset_count); + + if (reset_file.m_file_index != file_index) + ut_error; + + byte *block_data= buf.get() + ARCH_PAGE_BLK_HEADER_LENGTH; + + lsn_t file_reset_lsn= mach_read_from_8(block_data); + uint length= ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE; + + if (reset_file.m_lsn != file_reset_lsn) + ut_error; + + DBUG_PRINT("page_archiver", ("File lsn : %" PRIu64 "", file_reset_lsn)); + + uint index= 0; + Arch_Point start_point; + + while (length < data_length) + { + if (index >= reset_file.m_start_point.size()) + ut_error; + + start_point= reset_file.m_start_point.at(index); + + uint64_t block_num= mach_read_from_2(block_data + length); + length+= ARCH_PAGE_FILE_HEADER_RESET_BLOCK_NUM_SIZE; + + uint64_t block_offset= mach_read_from_2(block_data + length); + length+= ARCH_PAGE_FILE_HEADER_RESET_BLOCK_OFFSET_SIZE; + + if (block_num != start_point.pos.m_block_num || + block_offset != start_point.pos.m_offset) + ut_error; + + DBUG_PRINT("page_archiver", + ("Reset point %u : %" PRIu64 ", %" PRIu64 ", %" PRIu64 "", index, + start_point.lsn, block_num, block_offset)); + ++index; + } + + ut_ad(length == data_length); + + if (reset_file.m_start_point.size() != index) + ut_error; + + ++reset_count; + return true; +} + +bool Arch_Group::validate_info_in_files() +{ + uint reset_count= 0; + uint file_count= m_file_ctx.get_count(); + bool success= true; + + DBUG_PRINT("page_archiver", ("RESET PAGE")); + + for (uint file_index= 0; file_index < file_count; ++file_index) + { + bool last_file= file_index + 1 == file_count; + + if (last_file && m_file_ctx.get_phy_size() == 0) + { + success= false; + break; + } + + success= m_file_ctx.validate(this, file_index, m_begin_lsn, reset_count); + if (!success) + break; + } + + DBUG_PRINT("page_archiver", ("\n")); + return success; +} + +bool Arch_File_Ctx::validate(Arch_Group *group, uint file_index, + lsn_t start_lsn, uint &reset_count) +{ + char file_name[MAX_ARCH_PAGE_FILE_NAME_LEN]; + + build_name(file_index, start_lsn, file_name, MAX_ARCH_PAGE_FILE_NAME_LEN); + + os_file_type_t type; + bool exists= false; + bool ret; + + ret= os_file_status(file_name, &exists, &type); + + if (!ret || !exists) + /* Could be the case if files are purged. */ + return true; + + bool success; + pfs_os_file_t file; + + file= os_file_create(innodb_arch_file_key, file_name, OS_FILE_OPEN, + OS_CLONE_LOG_FILE, true, &success); + + if (!success) + return false; + + DBUG_PRINT("page_archiver", ("File : %u", file_index)); + success= validate_reset_block_in_file(file, file_index, reset_count); + + ut_ad(success); + if (!success) + { + if (file.m_file != OS_FILE_CLOSED) + os_file_close(file); + return false; + } + success= validate_stop_point_in_file(group, file, file_index); + + if (file.m_file != OS_FILE_CLOSED) + os_file_close(file); + + if (!success || + (file_index + 1 == m_count && reset_count != m_reset.size())) + ut_error; + return true; +} +#endif /* UNIV_DEBUG */ + +lsn_t Arch_File_Ctx::purge(lsn_t begin_lsn, lsn_t end_lsn, lsn_t purge_lsn) +{ + Arch_Point reset_point; + + /* Find reset lsn which is <= purge_lsn. */ + auto success= find_reset_point(purge_lsn, reset_point); + + if (!success || reset_point.lsn == begin_lsn) + { + const char* mesg= my_get_err_msg(ER_IB_MSG_PAGE_ARCH_NO_RESET_POINTS); + sql_print_information("%s", mesg); + return LSN_MAX; + } + + ut_ad(begin_lsn < reset_point.lsn && reset_point.lsn <= end_lsn); + + Arch_Reset_File file_reset_compare; + file_reset_compare.m_lsn= reset_point.lsn; + + /* Finds the file which has the element that is >= to reset_point.lsn. */ + auto reset_file_it= std::lower_bound( + m_reset.begin(), m_reset.end(), file_reset_compare, + [](const Arch_Reset_File &lhs, const Arch_Reset_File &rhs) + { + return (lhs.m_lsn < rhs.m_lsn); + }); + + /* The element that is less than check_lsn, which we're interested in, + will be in the previous position. */ + if (reset_file_it != m_reset.begin() && + (reset_file_it == m_reset.end() || reset_file_it->m_lsn != purge_lsn)) + --reset_file_it; + + if (reset_file_it == m_reset.begin()) + return LSN_MAX; + + lsn_t purged_lsn= reset_file_it->m_lsn; + + for (auto it= m_reset.begin(); it != reset_file_it;) + { + bool success= delete_file(it->m_file_index, begin_lsn); + + if (success) + /** Removes the deleted file from reset info, thereby incrementing the + iterator. */ + it= m_reset.erase(it); + else + { + purged_lsn= it->m_lsn; + reset_file_it= it; + ut_d(ut_error); + break; + } + } + + /** Only files which have a reset would be purged in the above loop. We want + to purge all the files preceding reset_file_it regardless of whether it has + a reset or not. */ + for (uint file_index= 0; file_index < reset_file_it->m_file_index; + ++file_index) + delete_file(file_index, begin_lsn); + + return purged_lsn; +} + +uint Arch_Group::purge(lsn_t purge_lsn, lsn_t &group_purged_lsn) +{ + mysql_mutex_assert_owner(m_arch_mutex); + + if (m_begin_lsn > purge_lsn) + { + group_purged_lsn= LSN_MAX; + return 0; + } + + /** For a group (active or non-active) if there are any non-durable clients + attached then we don't purge the group at all. */ + if (m_ref_count > 0) + { + group_purged_lsn= LSN_MAX; + return ER_PAGE_TRACKING_CANNOT_PURGE; + } + + if (!m_is_active && m_end_lsn <= purge_lsn) + { + m_file_ctx.delete_files(m_begin_lsn); + group_purged_lsn= m_end_lsn; + return 0; + } + + lsn_t purged_lsn= m_file_ctx.purge(m_begin_lsn, m_end_lsn, purge_lsn); + group_purged_lsn= purged_lsn; + return 0; +} + +#ifdef UNIV_DEBUG +void Page_Arch_Client_Ctx::print() +{ + DBUG_PRINT("page_archiver", ("CLIENT INFO")); + DBUG_PRINT("page_archiver", ("Transient Client - %u", !m_is_durable)); + DBUG_PRINT("page_archiver", ("Start LSN - %" PRIu64 "", m_start_lsn)); + DBUG_PRINT("page_archiver", ("Stop LSN - %" PRIu64 "", m_stop_lsn)); + DBUG_PRINT("page_archiver", + ("Last Reset LSN - %" PRIu64 "", m_last_reset_lsn)); + DBUG_PRINT("page_archiver", ("Start pos - %d , %u", + m_start_pos.m_block_num, m_start_pos.m_offset)); + DBUG_PRINT("page_archiver", ("Stop pos - %d , %u\n", + m_stop_pos.m_block_num, m_stop_pos.m_offset)); +} +#endif + +int Page_Arch_Client_Ctx::start(bool recovery, uint64_t *start_id) +{ + bool reset= false; + int err= 0; + + arch_client_mutex_enter(); + + switch (m_state) + { + case ARCH_CLIENT_STATE_STOPPED: + if (!m_is_durable) + { + arch_client_mutex_exit(); + return ER_PAGE_TRACKING_NOT_STARTED; + } + DBUG_PRINT("page_archiver", ("Archiver in progress")); + DBUG_PRINT("page_archiver", ("[->] Starting page archiving.")); + break; + + case ARCH_CLIENT_STATE_INIT: + DBUG_PRINT("page_archiver", ("Archiver in progress")); + DBUG_PRINT("page_archiver", ("[->] Starting page archiving.")); + break; + + case ARCH_CLIENT_STATE_STARTED: + DBUG_PRINT("page_archiver", ("[->] Resetting page archiving.")); + ut_ad(m_group != nullptr); + reset= true; + break; + + default: + ut_d(ut_error); + } + + /* Start archiving. */ + err= arch_sys->page_sys()->start(&m_group, &m_last_reset_lsn, &m_start_pos, + m_is_durable, reset, recovery); + if (err != 0) + { + arch_client_mutex_exit(); + return err; + } + + if (!reset) + m_start_lsn= m_last_reset_lsn; + + if (start_id != nullptr) + *start_id= m_last_reset_lsn; + + if (!is_active()) + m_state= ARCH_CLIENT_STATE_STARTED; + + arch_client_mutex_exit(); + + if (!m_is_durable) + { + /* Update DD table buffer to get rid of recovery dependency for auto INC */ + /* Auto INC is persisted differently in MariDB page_set_autoinc(). */ + // dict_persist_to_dd_table_buffer(); + + /* Make sure all written pages are synced to disk. */ + fil_flush_file_spaces(); + + ib::info() << "Clone Start PAGE ARCH : start LSN : " + << m_start_lsn << ", checkpoint LSN : " + << log_sys.last_checkpoint_lsn.load(); + } + return err; +} + +int Page_Arch_Client_Ctx::init_during_recovery(Arch_Group *group, + lsn_t last_lsn) +{ + /* Initialise the sys client */ + m_state= ARCH_CLIENT_STATE_STARTED; + m_group= group; + m_start_lsn= group->get_begin_lsn(); + m_last_reset_lsn= last_lsn; + m_start_pos.init(); + + /* Start page archiving. */ + int error= start(true, nullptr); + + ut_d(print()); + return error; +} + +int Page_Arch_Client_Ctx::stop(lsn_t *stop_id) +{ + arch_client_mutex_enter(); + + if (!is_active()) + { + arch_client_mutex_exit(); + const char* mesg= my_get_err_msg(ER_PAGE_TRACKING_NOT_STARTED); + sql_print_error("%s", mesg); + return ER_PAGE_TRACKING_NOT_STARTED; + } + + ut_ad(m_group != nullptr); + + /* Stop archiving. */ + auto err= arch_sys->page_sys()->stop(m_group, &m_stop_lsn, &m_stop_pos, + m_is_durable); + ut_d(print()); + + /* We stop the client even in cases of an error. */ + m_state= ARCH_CLIENT_STATE_STOPPED; + + if (stop_id != nullptr) { + *stop_id= m_stop_lsn; + } + + arch_client_mutex_exit(); + + ib::info() << "Clone Stop PAGE ARCH : end LSN : " << m_stop_lsn + << ", log sys LSN : " << log_sys.get_lsn_approx(); + + return err; +} + +int Page_Arch_Client_Ctx::get_pages(Page_Arch_Cbk *cbk_func, void *cbk_ctx, + byte *buff, uint buf_len) +{ + int err= 0; + uint num_pages; + uint read_len; + + arch_client_mutex_enter(); + + ut_ad(m_state == ARCH_CLIENT_STATE_STOPPED); + + auto cur_pos= m_start_pos; + + while (true) + { + ut_ad(cur_pos.m_block_num <= m_stop_pos.m_block_num); + + /* Check if last block */ + if (cur_pos.m_block_num >= m_stop_pos.m_block_num) + { + if (cur_pos.m_offset > m_stop_pos.m_offset) + { + my_error(ER_INTERNAL_ERROR, MYF(0), "Wrong Archiver page offset"); + err= ER_INTERNAL_ERROR; + ut_d(ut_error); + break; + } + read_len= m_stop_pos.m_offset - cur_pos.m_offset; + + if (read_len == 0) + break; + + } + else + { + if (cur_pos.m_offset > ARCH_PAGE_BLK_SIZE) + { + my_error(ER_INTERNAL_ERROR, MYF(0), "Wrong Archiver page offset"); + err= ER_INTERNAL_ERROR; + ut_d(ut_error); + break; + } + read_len= ARCH_PAGE_BLK_SIZE - cur_pos.m_offset; + + /* Move to next block. */ + if (read_len == 0) + { + cur_pos.set_next(); + continue; + } + } + + if (read_len > buf_len) + read_len = buf_len; + + err= m_group->read_data(cur_pos, buff, read_len); + if (err != 0) + break; + + cur_pos.m_offset+= read_len; + num_pages= read_len / ARCH_BLK_PAGE_ID_SIZE; + + err= cbk_func(cbk_ctx, buff, num_pages); + if (err != 0) + break; + } + arch_client_mutex_exit(); + return err; +} + +void Page_Arch_Client_Ctx::release() +{ + arch_client_mutex_enter(); + + switch (m_state) + { + case ARCH_CLIENT_STATE_INIT: + arch_client_mutex_exit(); + return; + + case ARCH_CLIENT_STATE_STARTED: + arch_client_mutex_exit(); + stop(nullptr); + break; + + case ARCH_CLIENT_STATE_STOPPED: + break; + + default: + ut_d(ut_error); + } + + ut_ad(m_group != nullptr); + arch_sys->page_sys()->release(m_group, m_is_durable, m_start_pos); + + m_state= ARCH_CLIENT_STATE_INIT; + m_group= nullptr; + m_start_lsn= LSN_MAX; + m_stop_lsn= LSN_MAX; + m_last_reset_lsn= LSN_MAX; + m_start_pos.init(); + m_stop_pos.init(); + + arch_client_mutex_exit(); +} + +bool wait_flush_archiver(Page_Wait_Flush_Archiver_Cbk cbk_func) +{ + auto arch_page_sys= arch_sys->page_sys(); + mysql_mutex_assert_owner(arch_page_sys->get_oper_mutex()); + + while (cbk_func()) + { + /* Need to wait for flush. We don't expect it + to happen normally. With no duplicate page ID + dirty page growth should be very slow. */ + arch_sys->signal_archiver(); + bool is_timeout= false; + int alert_count= 0; + auto thd= current_thd; + + auto err= Clone_Sys::wait_default( + [&](bool alert, bool &result) + { + mysql_mutex_assert_owner(arch_page_sys->get_oper_mutex()); + result= cbk_func(); + + int err2= 0; + if (srv_shutdown_state.load() == SRV_SHUTDOWN_LAST_PHASE || + srv_shutdown_state.load() == SRV_SHUTDOWN_EXIT_THREADS || + arch_page_sys->is_abort() || + (thd && thd_killed(thd))) + { + if (thd) my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err2= ER_QUERY_INTERRUPTED; + } + else if (result) + { + arch_sys->signal_archiver(); + if (alert && ++alert_count == 12) + { + alert_count= 0; + sql_print_information( + "Clone Page Tracking: waiting for block to flush"); + } + } + return err2; + }, + arch_page_sys->get_oper_mutex(), is_timeout); + + if (err != 0) + return false; + else if (is_timeout) + { + sql_print_warning("Clone Page Tracking: wait for block flush timed out"); + ut_d(ut_error); + return false; + } + } + return true; +} + +uint Arch_Block::get_file_index(uint64_t block_num, Arch_Blk_Type type) +{ + size_t file_index= std::numeric_limits::max(); + + switch (type) + { + case ARCH_RESET_BLOCK: + file_index= static_cast(block_num); + break; + + case ARCH_DATA_BLOCK: + file_index= static_cast(block_num) / ARCH_PAGE_FILE_DATA_CAPACITY; + break; + + default: + ut_d(ut_error); + } + return static_cast(file_index); +} + +bool Arch_Block::is_zeros(const void *start, size_t number_of_bytes) +{ + auto *first_byte= reinterpret_cast(start); + return number_of_bytes == 0 || (*first_byte == 0 && + std::memcmp(first_byte, first_byte + 1, number_of_bytes - 1) == 0); +} + +Arch_Blk_Type Arch_Block::get_type(byte *block) +{ + return static_cast( + mach_read_from_1(block + ARCH_PAGE_BLK_HEADER_TYPE_OFFSET)); +} + +uint Arch_Block::get_data_len(byte *block) +{ + return (mach_read_from_2(block + ARCH_PAGE_BLK_HEADER_DATA_LEN_OFFSET)); +} + +lsn_t Arch_Block::get_stop_lsn(byte *block) +{ + return (mach_read_from_8(block + ARCH_PAGE_BLK_HEADER_STOP_LSN_OFFSET)); +} + +uint64_t Arch_Block::get_block_number(byte *block) +{ + return (mach_read_from_8(block + ARCH_PAGE_BLK_HEADER_NUMBER_OFFSET)); +} + +lsn_t Arch_Block::get_reset_lsn(byte *block) +{ + return (mach_read_from_8(block + ARCH_PAGE_BLK_HEADER_RESET_LSN_OFFSET)); +} + +uint32_t Arch_Block::get_checksum(byte *block) +{ + return (mach_read_from_4(block + ARCH_PAGE_BLK_HEADER_CHECKSUM_OFFSET)); +} + +uint64_t Arch_Block::get_file_offset(uint64_t block_num, Arch_Blk_Type type) +{ + uint64_t offset= 0; + + switch (type) + { + case ARCH_RESET_BLOCK: + offset= 0; + break; + + case ARCH_DATA_BLOCK: + offset= block_num % ARCH_PAGE_FILE_DATA_CAPACITY; + offset+= ARCH_PAGE_FILE_NUM_RESET_PAGE; + offset*= ARCH_PAGE_BLK_SIZE; + break; + + default: + ut_d(ut_error); + } + return offset; +} + +bool Arch_Block::validate(byte *block) +{ + auto data_length= Arch_Block::get_data_len(block); + auto block_checksum= Arch_Block::get_checksum(block); + auto checksum= my_crc32c(0, block + ARCH_PAGE_BLK_HEADER_LENGTH, data_length); + + if (checksum != block_checksum) + { + const char* format= my_get_err_msg( + ER_IB_ERR_PAGE_ARCH_INVALID_DOUBLE_WRITE_BUF); + + my_printf_error(ER_IB_ERR_PAGE_ARCH_INVALID_DOUBLE_WRITE_BUF, format, + MYF(ME_ERROR_LOG_ONLY|ME_WARNING), + Arch_Block::get_block_number(block)); + ut_d(ut_error); + return false; + } + else if (Arch_Block::is_zeros(block, ARCH_PAGE_BLK_SIZE)) + return false; + + return true; +} + +void Arch_Block::update_block_header(lsn_t stop_lsn, lsn_t reset_lsn) +{ + mach_write_to_2(m_data + ARCH_PAGE_BLK_HEADER_DATA_LEN_OFFSET, m_data_len); + + if (stop_lsn != LSN_MAX) + { + m_stop_lsn= stop_lsn; + mach_write_to_8(m_data + ARCH_PAGE_BLK_HEADER_STOP_LSN_OFFSET, m_stop_lsn); + } + + if (reset_lsn != LSN_MAX) + { + m_reset_lsn= reset_lsn; + mach_write_to_8(m_data + ARCH_PAGE_BLK_HEADER_RESET_LSN_OFFSET, + m_reset_lsn); + } +} + +/** Set the block ready to begin writing page ID +@param[in] pos position to initiate block number */ +void Arch_Block::begin_write(Arch_Page_Pos pos) +{ + m_data_len= 0; + m_state= ARCH_BLOCK_ACTIVE; + + m_number= + (m_type == ARCH_DATA_BLOCK + ? pos.m_block_num + : Arch_Block::get_file_index(pos.m_block_num, ARCH_DATA_BLOCK)); + + m_oldest_lsn= LSN_MAX; + m_reset_lsn= LSN_MAX; + + if (m_type == ARCH_DATA_BLOCK) + arch_sys->page_sys()->update_stop_info(this); +} + +/** End writing to a block. +Change state to #ARCH_BLOCK_READY_TO_FLUSH */ +void Arch_Block::end_write() { m_state = ARCH_BLOCK_READY_TO_FLUSH; } + +/** Add page ID to current block +@param[in] page page from buffer pool +@param[in] pos Archiver current position +@return true, if successful + false, if no more space in current block */ +bool Arch_Block::add_page(buf_page_t *page, Arch_Page_Pos *pos) +{ + space_id_t space_id; + page_no_t page_num; + byte *data_ptr; + + ut_ad(pos->m_offset <= ARCH_PAGE_BLK_SIZE); + ut_ad(m_type == ARCH_DATA_BLOCK); + ut_ad(pos->m_offset == m_data_len + ARCH_PAGE_BLK_HEADER_LENGTH); + + if ((pos->m_offset + ARCH_BLK_PAGE_ID_SIZE) > ARCH_PAGE_BLK_SIZE) + { + ut_ad(pos->m_offset == ARCH_PAGE_BLK_SIZE); + return false; + } + + data_ptr= m_data + pos->m_offset; + + /* Write serialized page ID: tablespace ID and offset */ + space_id= page->id().space(); + page_num= page->id().page_no(); + + mach_write_to_4(data_ptr + ARCH_BLK_SPCE_ID_OFFSET, space_id); + mach_write_to_4(data_ptr + ARCH_BLK_PAGE_NO_OFFSET, page_num); + + /* Update position. */ + pos->m_offset+= ARCH_BLK_PAGE_ID_SIZE; + m_data_len+= ARCH_BLK_PAGE_ID_SIZE; + + /* Update oldest LSN from page. */ + if (arch_sys->page_sys()->get_latest_stop_lsn() > m_oldest_lsn || + m_oldest_lsn > page->oldest_modification()) + m_oldest_lsn = page->oldest_modification(); + + return true; +} + +bool Arch_Block::get_data(Arch_Page_Pos *read_pos, uint read_len, + byte *read_buff) +{ + ut_ad(read_pos->m_offset + read_len <= m_size); + + if (m_state == ARCH_BLOCK_INIT || m_number != read_pos->m_block_num) + /* The block is already overwritten. */ + return false; + + byte *src= m_data + read_pos->m_offset; + memcpy(read_buff, src, read_len); + + return true; +} + +bool Arch_Block::set_data(uint read_len, byte *read_buff, uint read_offset) +{ + ut_ad(m_state != ARCH_BLOCK_INIT); + ut_ad(read_offset + read_len <= m_size); + + byte *dest= m_data + read_offset; + memcpy(dest, read_buff, read_len); + + set_reset_lsn(Arch_Block::get_reset_lsn(m_data)); + return true; +} + +/** Flush this block to the file group. +@param[in] file_group current archive group +@param[in] type flush type +@return error code. */ +dberr_t Arch_Block::flush(Arch_Group *file_group, Arch_Blk_Flush_Type type) +{ + dberr_t err= DB_SUCCESS; + uint32_t checksum= my_crc32c(0, m_data + ARCH_PAGE_BLK_HEADER_LENGTH, + m_data_len); + /* Update block's header. */ + mach_write_to_1(m_data + ARCH_PAGE_BLK_HEADER_VERSION_OFFSET, + ARCH_PAGE_FILE_VERSION); + mach_write_to_1(m_data + ARCH_PAGE_BLK_HEADER_TYPE_OFFSET, m_type); + mach_write_to_2(m_data + ARCH_PAGE_BLK_HEADER_DATA_LEN_OFFSET, m_data_len); + mach_write_to_4(m_data + ARCH_PAGE_BLK_HEADER_CHECKSUM_OFFSET, checksum); + mach_write_to_8(m_data + ARCH_PAGE_BLK_HEADER_STOP_LSN_OFFSET, m_stop_lsn); + mach_write_to_8(m_data + ARCH_PAGE_BLK_HEADER_RESET_LSN_OFFSET, m_reset_lsn); + mach_write_to_8(m_data + ARCH_PAGE_BLK_HEADER_NUMBER_OFFSET, m_number); + + switch (m_type) + { + case ARCH_RESET_BLOCK: + err= file_group->write_file_header(m_data, m_size); + break; + + case ARCH_DATA_BLOCK: + { + bool is_partial_flush= (type == ARCH_FLUSH_PARTIAL); + + /* We allow partial flush to happen even if there were no pages added + since the last partial flush as the block's header might contain some + useful info required during recovery. */ + err= file_group->write_to_file(nullptr, m_data, m_size, is_partial_flush, + true); + break; + } + + default: + ut_d(ut_error); + } + return err; +} + +void Arch_Block::add_reset(lsn_t reset_lsn, Arch_Page_Pos reset_pos) +{ + ut_ad(m_type == ARCH_RESET_BLOCK); + ut_ad(m_data_len <= ARCH_PAGE_BLK_SIZE); + ut_ad(m_data_len + ARCH_PAGE_FILE_HEADER_RESET_POS_SIZE <= + ARCH_PAGE_BLK_SIZE); + + byte *buf= m_data + ARCH_PAGE_BLK_HEADER_LENGTH; + + if (m_data_len == 0) + { + /* Write file lsn. */ + mach_write_to_8(buf, reset_lsn); + m_data_len+= ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE; + } + + ut_ad(m_data_len >= ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE); + + mach_write_to_2(buf + m_data_len, reset_pos.m_block_num); + m_data_len+= ARCH_PAGE_FILE_HEADER_RESET_BLOCK_NUM_SIZE; + + mach_write_to_2(buf + m_data_len, reset_pos.m_offset); + m_data_len+= ARCH_PAGE_FILE_HEADER_RESET_BLOCK_OFFSET_SIZE; +} + +void Arch_Block::copy_data(const Arch_Block *block) +{ + m_data_len= block->m_data_len; + m_size= block->m_size; + m_state= block->m_state; + m_number= block->m_number; + m_type= block->m_type; + m_stop_lsn= block->m_stop_lsn; + m_reset_lsn= block->m_reset_lsn; + m_oldest_lsn= block->m_oldest_lsn; + set_data(m_size, block->m_data, 0); +} + +/** Initialize a position */ +void Arch_Page_Pos::init() +{ + m_block_num= 0; + m_offset= ARCH_PAGE_BLK_HEADER_LENGTH; +} + +/** Position in the beginning of next block */ +void Arch_Page_Pos::set_next() +{ + m_block_num++; + m_offset= ARCH_PAGE_BLK_HEADER_LENGTH; +} + +/** Allocate buffer and initialize blocks +@return true, if successful */ +bool ArchPageData::init() +{ + uint alloc_size; + uint index; + byte *mem_ptr; + + ut_ad(m_buffer == nullptr); + + m_block_size= ARCH_PAGE_BLK_SIZE; + m_num_data_blocks= ARCH_PAGE_NUM_BLKS; + + /* block size and number must be in power of 2 */ + ut_ad(ut_is_2pow(m_block_size)); + ut_ad(ut_is_2pow(m_num_data_blocks)); + + alloc_size= m_block_size * m_num_data_blocks; + + /* For reset block. */ + alloc_size+= m_block_size; + + /* For partial flush block. */ + alloc_size+= m_block_size; + + /* For alignment */ + alloc_size+= m_block_size; + + /* Allocate buffer for memory blocks. */ + m_buffer= static_cast(ut_zalloc(alloc_size, mem_key_archive)); + + if (m_buffer == nullptr) + return false; + + mem_ptr = static_cast( + ut_align_down(m_buffer + m_block_size, m_block_size)); + + Arch_Block *cur_blk; + + /* Create memory blocks. */ + for (index= 0; index < m_num_data_blocks; index++) + { + cur_blk= UT_NEW(Arch_Block(mem_ptr, m_block_size, ARCH_DATA_BLOCK), + mem_key_archive); + if (cur_blk == nullptr) + return false; + + m_data_blocks.push_back(cur_blk); + mem_ptr+= m_block_size; + } + m_reset_block= UT_NEW(Arch_Block(mem_ptr, m_block_size, ARCH_RESET_BLOCK), + mem_key_archive); + if (m_reset_block == nullptr) + return false; + + mem_ptr+= m_block_size; + + m_partial_flush_block= + UT_NEW(Arch_Block(mem_ptr, m_block_size, ARCH_DATA_BLOCK), + mem_key_archive); + if (m_partial_flush_block == nullptr) + return false; + + return true; +} + +/** Delete blocks and buffer */ +void ArchPageData::clean() +{ + for (auto &block : m_data_blocks) + { + UT_DELETE(block); + block= nullptr; + } + + if (m_reset_block != nullptr) + { + UT_DELETE(m_reset_block); + m_reset_block= nullptr; + } + + if (m_partial_flush_block != nullptr) + { + UT_DELETE(m_partial_flush_block); + m_partial_flush_block= nullptr; + } + ut_free(m_buffer); +} + +/** Get the block for a position +@param[in] pos position in page archive sys +@param[in] type block type +@return page archive in memory block */ +Arch_Block *ArchPageData::get_block(Arch_Page_Pos *pos, Arch_Blk_Type type) +{ + switch (type) + { + case ARCH_DATA_BLOCK: + { + /* index = block_num % m_num_blocks */ + ut_ad(ut_is_2pow(m_num_data_blocks)); + + auto index= pos->m_block_num & (m_num_data_blocks - 1); + return m_data_blocks[index]; + } + case ARCH_RESET_BLOCK: + return m_reset_block; + + default: + ut_d(ut_error); + } + return nullptr; +} + +Arch_Page_Sys::Arch_Page_Sys() +{ + mysql_mutex_init(0, &m_mutex, nullptr); + mysql_mutex_init(0, &m_oper_mutex, nullptr); + + m_ctx= UT_NEW(Page_Arch_Client_Ctx(true), mem_key_archive); + + DBUG_EXECUTE_IF("page_archiver_simulate_more_archived_files", + ARCH_PAGE_FILE_CAPACITY = 8; + ARCH_PAGE_FILE_DATA_CAPACITY = + ARCH_PAGE_FILE_CAPACITY - ARCH_PAGE_FILE_NUM_RESET_PAGE;); +} + +Arch_Page_Sys::~Arch_Page_Sys() +{ + ut_ad(m_state == ARCH_STATE_INIT || m_state == ARCH_STATE_ABORT || + m_state == ARCH_STATE_READ_ONLY); + ut_ad(m_current_group == nullptr); + + for (auto group : m_group_list) + UT_DELETE(group); + + Arch_Group::shutdown(); + m_data.clean(); + + UT_DELETE(m_ctx); + mysql_mutex_destroy(&m_mutex); + mysql_mutex_destroy(&m_oper_mutex); +} + +void Arch_Page_Sys::post_recovery_init() +{ + if (!is_active()) + return; + + arch_oper_mutex_enter(); + m_latest_stop_lsn= + log_sys.last_checkpoint_lsn.load(std::memory_order_seq_cst); + auto cur_block= m_data.get_block(&m_write_pos, ARCH_DATA_BLOCK); + update_stop_info(cur_block); + arch_oper_mutex_exit(); +} + +void Arch_Page_Sys::flush_at_checkpoint(lsn_t checkpoint_lsn) +{ + arch_oper_mutex_enter(); + + if (!is_active()) + { + arch_oper_mutex_exit(); + return; + } + + lsn_t end_lsn= m_current_group->get_end_lsn(); + + if (m_write_pos.m_offset == ARCH_PAGE_BLK_HEADER_LENGTH) + { + arch_oper_mutex_exit(); + return; + } + + Arch_Page_Pos request_flush_pos; + + if (end_lsn == LSN_MAX) + { + Arch_Block *cur_block = m_data.get_block(&m_write_pos, ARCH_DATA_BLOCK); + + ut_ad(cur_block->get_state() == ARCH_BLOCK_ACTIVE); + + m_latest_stop_lsn= checkpoint_lsn; + update_stop_info(cur_block); + + if (cur_block->get_oldest_lsn() != LSN_MAX && + cur_block->get_oldest_lsn() <= checkpoint_lsn) + /* If the oldest modified page in the block added since the last + checkpoint was modified before the checkpoint_lsn then the block needs to + be flushed*/ + request_flush_pos = m_write_pos; + else + { + /* Wait for blocks that are not active to be flushed. */ + + if (m_write_pos.m_block_num == 0) + { + arch_oper_mutex_exit(); + return; + } + request_flush_pos.init(); + request_flush_pos.m_block_num= m_write_pos.m_block_num - 1; + } + + if (request_flush_pos < m_flush_pos) + { + arch_oper_mutex_exit(); + return; + } + + if (m_request_flush_pos < request_flush_pos) + m_request_flush_pos= request_flush_pos; + } + else + { + request_flush_pos= m_current_group->get_stop_pos(); + m_request_flush_pos= request_flush_pos; + } + + if (request_flush_pos.m_block_num == m_write_pos.m_block_num) + MONITOR_INC(MONITOR_PAGE_TRACK_CHECKPOINT_PARTIAL_FLUSH_REQUEST); + + /* We need to ensure that blocks are flushed until request_flush_pos */ + auto cbk = [&] { return (request_flush_pos < m_flush_pos ? false : true); }; + + if (!wait_flush_archiver(cbk)) + { + const char* mesg= my_get_err_msg(ER_IB_WRN_PAGE_ARCH_FLUSH_DATA); + sql_print_warning("%s", mesg); + } + arch_oper_mutex_exit(); +} + +void Arch_Page_Sys::track_page(buf_page_t *bpage, lsn_t track_lsn, + lsn_t oldest_lsn, bool track_mark) +{ + Arch_Block *cur_blk; + uint count= 0; + + if (oldest_lsn > track_lsn && !track_mark) + /* If the LSN is bigger than track LSNand track mark is not set, it + is already added to tracking list. */ + return; + + /* We need to track this page. */ + arch_oper_mutex_enter(); + + while (true) + { + if (m_state != ARCH_STATE_ACTIVE) + break; + + /* Can possibly loop only two times. */ + if (count >= 2) + { + if (srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP) + { + arch_oper_mutex_exit(); + return; + } + ib::warn() << "Fail to add page for tracking." + << " Space ID: " << bpage->id().space(); + + m_state= ARCH_STATE_ABORT; + arch_oper_mutex_exit(); + ut_d(ut_error); + return; + } + + cur_blk= m_data.get_block(&m_write_pos, ARCH_DATA_BLOCK); + + if (cur_blk->get_state() == ARCH_BLOCK_ACTIVE) + { + if (cur_blk->add_page(bpage, &m_write_pos)) + /* page added successfully. */ + break; + + /* Current block is full. Move to next block. */ + cur_blk->end_write(); + m_write_pos.set_next(); + + /* Writing to a new file so move to the next reset block. */ + if (m_write_pos.m_block_num % ARCH_PAGE_FILE_DATA_CAPACITY == 0) + { + Arch_Block *reset_block= + m_data.get_block(&m_reset_pos, ARCH_RESET_BLOCK); + reset_block->end_write(); + m_reset_pos.set_next(); + } + arch_sys->signal_archiver(); + + ++count; + continue; + + } + else if (cur_blk->get_state() == ARCH_BLOCK_INIT || + cur_blk->get_state() == ARCH_BLOCK_FLUSHED) + { + ut_ad(m_write_pos.m_offset == ARCH_PAGE_BLK_HEADER_LENGTH); + cur_blk->begin_write(m_write_pos); + + if (!cur_blk->add_page(bpage, &m_write_pos)) { + /* Should always succeed. */ + ut_d(ut_error); + } + /* page added successfully. */ + break; + + } + else + { + ut_a(cur_blk->get_state() == ARCH_BLOCK_READY_TO_FLUSH); + auto cbk= std::bind(&Arch_Block::is_flushable, *cur_blk); + + /* Might release operation mutex temporarily. Need to + loop again verifying the state. */ + bool success= wait_flush_archiver(cbk); + count= success ? 0 : 2; + continue; + } + } + arch_oper_mutex_exit(); +} + +/** Get page IDs from a specific position. +Caller must ensure that read_len doesn't exceed the block. +@param[in] group group whose pages we're interested in +@param[in] read_pos position in archived data +@param[in] read_len amount of data to read +@param[out] read_buff buffer to return the page IDs. +@note Caller must allocate the buffer. +@return true if we could successfully read the block. */ +bool Arch_Page_Sys::get_pages(Arch_Group *group, Arch_Page_Pos *read_pos, + uint read_len, byte *read_buff) +{ + arch_oper_mutex_enter(); + + if (group != m_current_group) + { + arch_oper_mutex_exit(); + return false; + } + + /* Get the block to read from. */ + auto read_blk= m_data.get_block(read_pos, ARCH_DATA_BLOCK); + read_blk->update_block_header(LSN_MAX, LSN_MAX); + + /* Read from the block. */ + bool success= read_blk->get_data(read_pos, read_len, read_buff); + + arch_oper_mutex_exit(); + return success; +} + +int Arch_Page_Sys::get_pages(MYSQL_THD thd, Page_Track_Callback cbk_func, + void *cbk_ctx, lsn_t &start_id, lsn_t &stop_id, + byte *buf, uint buf_len) +{ + DBUG_PRINT("page_archiver", ("Fetch pages")); + arch_mutex_enter(); + + if (m_state == ARCH_STATE_READ_ONLY) + { + arch_mutex_exit(); + return 0; + } + /** 1. Get appropriate LSN range. */ + Arch_Group *group= nullptr; + + int error= fetch_group_within_lsn_range(start_id, stop_id, &group); + DBUG_PRINT("page_archiver", ("Start id: %" PRIu64 ", stop id: %" PRIu64 "", + start_id, stop_id)); + if (error != 0) + { + arch_mutex_exit(); + return error; + } + ut_ad(group != nullptr); + + /** 2. Get block position from where to start. */ + Arch_Point reset_point; + + auto success= group->find_reset_point(start_id, reset_point); + Arch_Page_Pos start_pos = reset_point.pos; + start_id= reset_point.lsn; + + if (!success) + { + arch_mutex_exit(); + DBUG_PRINT("page_archiver", + ("Can't fetch pages - No matching reset point.")); + return ER_PAGE_TRACKING_RANGE_NOT_TRACKED; + } + + /* 3. Fetch tracked pages. */ + DBUG_PRINT("page_archiver", + ("Trying to get pages between %" PRIu64 " to %" PRIu64 "", + start_id, stop_id)); + + byte header_buf[ARCH_PAGE_BLK_HEADER_LENGTH]; + + int err= 0; + auto cur_pos= start_pos; + Arch_Page_Pos temp_pos; + uint num_pages; + bool new_block= true; + bool last_block= false; + lsn_t block_stop_lsn= LSN_MAX; + uint read_len= 0; + uint bytes_left= 0; + + arch_oper_mutex_enter(); + auto end_lsn= group->get_end_lsn(); + + Arch_Page_Pos last_pos= + (end_lsn == LSN_MAX) ? m_write_pos : group->get_stop_pos(); + arch_oper_mutex_exit(); + + while (true) + { + if (new_block) + { + temp_pos.m_block_num= cur_pos.m_block_num; + temp_pos.m_offset= 0; + + /* Read the block header for data length and stop lsn info. */ + err= group->read_data(temp_pos, header_buf, ARCH_PAGE_BLK_HEADER_LENGTH); + if (err != 0) + break; + + block_stop_lsn= Arch_Block::get_stop_lsn(header_buf); + auto data_len= Arch_Block::get_data_len(header_buf); + bytes_left= data_len + ARCH_PAGE_BLK_HEADER_LENGTH; + + ut_ad(bytes_left <= ARCH_PAGE_BLK_SIZE); + ut_ad(block_stop_lsn != LSN_MAX); + + bytes_left-= cur_pos.m_offset; + + if (data_len == 0 || cur_pos.m_block_num == last_pos.m_block_num || + block_stop_lsn > stop_id) + { + ut_ad(block_stop_lsn >= stop_id); + stop_id= block_stop_lsn; + last_block= true; + } + + DBUG_PRINT("page_archiver", + ("%d -> length : %u, stop lsn : %" PRIu64 + ", last block : %d", + cur_pos.m_block_num, data_len, block_stop_lsn, last_block)); + } + + ut_ad(cur_pos.m_offset <= ARCH_PAGE_BLK_SIZE); + + /* Read how much ever is left to be read in the block. */ + read_len= bytes_left; + + if (last_block && read_len == 0) + /* There is nothing to read. */ + break; + + if (read_len > buf_len) + read_len= buf_len; + + /* Read the block for list of pages */ + err= group->read_data(cur_pos, buf, read_len); + if (err != 0) + break; + + cur_pos.m_offset+= read_len; + bytes_left-= read_len; + num_pages= read_len / ARCH_BLK_PAGE_ID_SIZE; + + err= cbk_func(thd, buf, buf_len, num_pages, cbk_ctx); + if (err != 0) + break; + + if (bytes_left == 0) + { + /* We have read all the pages in the block. */ + + if (last_block) + break; + else + { + new_block= true; + bytes_left= 0; + read_len= 0; + cur_pos.set_next(); + continue; + } + } + else + /* We still have some bytes to read from the current block. */ + new_block = false; + } + arch_mutex_exit(); + return 0; +} + +bool Arch_Page_Sys::get_num_pages(Arch_Page_Pos start_pos, + Arch_Page_Pos stop_pos, uint64_t &num_pages) +{ + if (start_pos.m_block_num > stop_pos.m_block_num || + ((start_pos.m_block_num == stop_pos.m_block_num) && + (start_pos.m_offset >= stop_pos.m_offset))) + return false; + + uint64_t length= 0; + + if (start_pos.m_block_num != stop_pos.m_block_num) + { + length = ARCH_PAGE_BLK_SIZE - start_pos.m_offset; + length += stop_pos.m_offset - ARCH_PAGE_BLK_HEADER_LENGTH; + + auto num_blocks = stop_pos.m_block_num - start_pos.m_block_num - 1; + length += num_blocks * (ARCH_PAGE_BLK_SIZE - ARCH_PAGE_BLK_HEADER_LENGTH); + + } + else + length = stop_pos.m_offset - start_pos.m_offset; + + num_pages= length / ARCH_BLK_PAGE_ID_SIZE; + return true; +} + +int Arch_Page_Sys::get_num_pages(lsn_t &start_id, lsn_t &stop_id, + uint64_t *num_pages) +{ + DBUG_PRINT("page_archiver", ("Fetch num pages")); + + arch_mutex_enter(); + /** 1. Get appropriate LSN range. */ + Arch_Group *group= nullptr; + int error= fetch_group_within_lsn_range(start_id, stop_id, &group); + +#ifdef UNIV_DEBUG + arch_oper_mutex_enter(); + DBUG_PRINT("page_archiver", ("Start id: %" PRIu64 ", stop id: %" PRIu64 "", + start_id, stop_id)); + if (is_active()) + DBUG_PRINT("page_archiver", + ("Write_pos : %d, %u", m_write_pos.m_block_num, + m_write_pos.m_offset)); + DBUG_PRINT("page_archiver", + ("Latest stop lsn : %" PRIu64 "", m_latest_stop_lsn)); + + arch_oper_mutex_exit(); +#endif + if (error != 0) + { + arch_mutex_exit(); + return error; + } + ut_ad(group != nullptr); + + /** 2. Get block position from where to start. */ + Arch_Point start_point; + bool success = group->find_reset_point(start_id, start_point); + + if (!success) + { + DBUG_PRINT("page_archiver", + ("Can't fetch pages - No matching reset point.")); + arch_mutex_exit(); + return ER_PAGE_TRACKING_RANGE_NOT_TRACKED; + } + + DBUG_PRINT( + "page_archiver", + ("Start point - lsn : %" PRIu64 " \tpos : %d , %u", + start_point.lsn, start_point.pos.m_block_num, start_point.pos.m_offset)); + + Arch_Page_Pos start_pos = start_point.pos; + start_id= start_point.lsn; + + /** 3. Get block position where to stop */ + Arch_Point stop_point; + + success= group->find_stop_point(stop_id, stop_point, m_write_pos); + ut_ad(success); + + DBUG_PRINT( + "page_archiver", + ("Stop point - lsn : %" PRIu64 " \tpos : %d , %u", stop_point.lsn, + stop_point.pos.m_block_num, stop_point.pos.m_offset)); + + arch_mutex_exit(); + + Arch_Page_Pos stop_pos= stop_point.pos; + stop_id= stop_point.lsn; + + /** 4. Fetch number of pages tracked. */ + ut_ad(start_point.lsn <= stop_point.lsn); + ut_ad(start_point.pos.m_block_num <= stop_point.pos.m_block_num); + + success= get_num_pages(start_pos, stop_pos, *num_pages); + + if (!success) + num_pages = nullptr; + + DBUG_PRINT("page_archiver", + ("Number of pages tracked : %" PRIu64 "", *num_pages)); + return 0; +} + +/** Wait for archive system to come out of #ARCH_STATE_PREPARE_IDLE. +If the system is preparing to idle, #start needs to wait +for it to come to idle state. +@return true, if successful + false, if needs to abort */ +bool Arch_Page_Sys::wait_idle() +{ + mysql_mutex_assert_owner(&m_mutex); + + if (m_state == ARCH_STATE_PREPARE_IDLE) + { + arch_sys->signal_archiver(); + bool is_timeout= false; + int alert_count= 0; + auto thd= current_thd; + + auto err= Clone_Sys::wait_default( + [&](bool alert, bool &result) + { + mysql_mutex_assert_owner(&m_mutex); + result= (m_state == ARCH_STATE_PREPARE_IDLE); + + if (srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP || + (thd && thd_killed(thd))) + { + if (thd) my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + if (result) + { + arch_sys->signal_archiver(); + /* Print messages every 1 minute - default is 5 seconds. */ + if (alert && ++alert_count == 12) + { + alert_count= 0; + sql_print_information( + "Page Tracking start: waiting for idle state."); + } + } + return 0; + }, + &m_mutex, is_timeout); + + if (err == 0 && is_timeout) + { + err= ER_INTERNAL_ERROR; + sql_print_information( + "Page Tracking start: wait for idle state timed out"); + ut_d(ut_error); + } + if (err != 0) + return false; + } + return true; +} + +/** Check if the gap from last reset is short. +If not many page IDs are added till last reset, we avoid taking a new reset +point +@return true, if the gap is small. */ +bool Arch_Page_Sys::is_gap_small() +{ + ut_ad(m_last_pos.m_block_num <= m_write_pos.m_block_num); + + if (m_last_pos.m_block_num == m_write_pos.m_block_num) + return true; + + auto next_block_num= m_last_pos.m_block_num + 1; + auto length= ARCH_PAGE_BLK_SIZE - m_last_pos.m_offset; + + if (next_block_num != m_write_pos.m_block_num) + return false; + + length+= m_write_pos.m_offset - ARCH_PAGE_BLK_HEADER_LENGTH; + + /* Pages added since last reset. */ + auto num_pages= length / ARCH_BLK_PAGE_ID_SIZE; + return num_pages < ARCH_PAGE_RESET_THRESHOLD; +} + +/** Track pages for which IO is already started. */ +void Arch_Page_Sys::track_initial_pages() +{ + /* Page tracking must already be active. */ + ut_ad(buf_pool.is_tracking().first); + + mysql_mutex_lock(&buf_pool.flush_list_mutex); + buf_page_t *bpage= UT_LIST_GET_LAST(buf_pool.flush_list); + + /* Add all pages for which IO is already started. */ + while (bpage != nullptr) + { + if (fsp_is_system_temporary(bpage->id().space())) + { + bpage= UT_LIST_GET_PREV(list, bpage); + continue; + } + /* Check if we could finish traversing flush list earlier. */ + if (buf_pool.is_lsn_more_than_max_io_lsn(bpage->oldest_modification())) + { + /* All pages with oldest_modification smaller than + bpage->oldest_modification have already been traversed. */ + break; + } + if (bpage->is_write_fixed()) + /* IO has already started. Must add the page */ + track_page(bpage, LSN_MAX, LSN_MAX, true); + bpage= UT_LIST_GET_PREV(list, bpage); + } + mysql_mutex_unlock(&buf_pool.flush_list_mutex); +} + +/** Enable tracking pages in all buffer pools. +@param[in] tracking_lsn track pages from this LSN */ +void Arch_Page_Sys::set_tracking_buf_pool(lsn_t tracking_lsn) +{ + mysql_mutex_lock(&buf_pool.mutex); + buf_pool.set_tracking(tracking_lsn); + mysql_mutex_unlock(&buf_pool.mutex); +} + +int Arch_Page_Sys::recovery_load_and_start(const Arch_Recv_Group_Info &info) +{ + /* Initialise the page archiver with the info parsed from the files. */ + + m_current_group= info.m_group; + + m_write_pos= info.m_write_pos; + m_reset_pos= info.m_reset_pos; + m_flush_pos= m_write_pos; + + Arch_Reset_File last_reset_file= info.m_last_reset_file; + ut_ad(last_reset_file.m_start_point.size() > 0); + Arch_Point reset_point= last_reset_file.m_start_point.back(); + + m_last_pos= reset_point.pos; + m_last_lsn= reset_point.lsn; + m_last_reset_file_index= last_reset_file.m_file_index; + + ut_ad(m_last_lsn != LSN_MAX); + + auto err= m_ctx->init_during_recovery(m_current_group, m_last_lsn); + if (err != 0) + return err; + + if (info.m_new_empty_file) + { + m_flush_pos.set_next(); + m_write_pos.set_next(); + m_reset_pos.set_next(); + m_last_reset_file_index= m_reset_pos.m_block_num; + } + + /* Reload both reset block and write block active at the time of a crash. */ + auto cur_blk= m_data.get_block(&m_write_pos, ARCH_DATA_BLOCK); + auto reset_block= m_data.get_block(&m_reset_pos, ARCH_RESET_BLOCK); + + arch_mutex_enter(); + arch_oper_mutex_enter(); + + cur_blk->begin_write(m_write_pos); + reset_block->begin_write(m_write_pos); + + if (!info.m_new_empty_file) + { + cur_blk->set_data_len(m_write_pos.m_offset - ARCH_PAGE_BLK_HEADER_LENGTH); + cur_blk->set_data(ARCH_PAGE_BLK_SIZE, info.m_last_data_block, 0); + + reset_block->set_data_len(m_reset_pos.m_offset - + ARCH_PAGE_BLK_HEADER_LENGTH); + reset_block->set_data(ARCH_PAGE_BLK_SIZE, info.m_last_reset_block, 0); + } + + ut_d(print()); + + arch_oper_mutex_exit(); + arch_mutex_exit(); + return err; +} + +int Arch_Page_Sys::start(Arch_Group **group, lsn_t *start_lsn, + Arch_Page_Pos *start_pos, bool is_durable, + bool restart, bool recovery) +{ + /* Check if archiver task needs to be started. */ + arch_mutex_enter(); + + if (m_state == ARCH_STATE_READ_ONLY) + { + arch_mutex_exit(); + return 0; + } + + bool start_archiver= true; + bool attach_to_current= false; + bool acquired_oper_mutex= false; + + lsn_t log_sys_lsn= LSN_MAX; + + start_archiver= is_init(); + + /* Wait for idle state, if preparing to idle. */ + if (!wait_idle()) + { + int err= 0; + + if (srv_shutdown_state.load() >= SRV_SHUTDOWN_CLEANUP) + { + err= ER_QUERY_INTERRUPTED; + my_error(err, MYF(0)); + } + else + { + err= ER_INTERNAL_ERROR; + my_error(err, MYF(0), "Page Archiver wait too long"); + } + + arch_mutex_exit(); + return err; + } + + switch (m_state) + { + case ARCH_STATE_ABORT: + arch_mutex_exit(); + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + + case ARCH_STATE_INIT: + case ARCH_STATE_IDLE: + [[fallthrough]]; + + case ARCH_STATE_ACTIVE: + + if (m_current_group != nullptr) + { + /* If gap is small, just attach to current group */ + attach_to_current= (recovery ? false : is_gap_small()); + + if (attach_to_current) + DBUG_PRINT("page_archiver", + ("Gap is small - last pos : %d" + " %u, write_pos : %d %u", + m_last_pos.m_block_num, m_last_pos.m_offset, + m_write_pos.m_block_num, m_write_pos.m_offset)); + } + + if (!attach_to_current) + { + log_sys.latch.wr_lock(SRW_LOCK_CALL); + + if (!recovery) + MONITOR_INC(MONITOR_PAGE_TRACK_RESETS); + + log_sys_lsn= recovery ? m_last_lsn : log_sys.get_lsn(); + + /* Enable/Reset buffer pool page tracking. */ + set_tracking_buf_pool(log_sys_lsn); + + /* Take operation mutex before releasing log_sys to + ensure that all pages modified after log_sys_lsn are + tracked. */ + arch_oper_mutex_enter(); + acquired_oper_mutex= true; + + log_sys.latch.wr_unlock(); + } + else + { + arch_oper_mutex_enter(); + acquired_oper_mutex= true; + } + break; + + case ARCH_STATE_PREPARE_IDLE: + default: + ut_d(ut_error); + } + + if (is_init() && !m_data.init()) + { + ut_ad(!attach_to_current); + acquired_oper_mutex= false; + arch_oper_mutex_exit(); + arch_mutex_exit(); + + my_error(ER_OUTOFMEMORY, MYF(0), ARCH_PAGE_BLK_SIZE); + return ER_OUTOFMEMORY; + } + + /* Start archiver background task. */ + if (start_archiver) + { + ut_ad(!attach_to_current); + auto err= arch_sys->start_archiver(); + + if (err != 0) + { + acquired_oper_mutex= false; + arch_oper_mutex_exit(); + arch_mutex_exit(); + + sql_print_error("Could not start Page Archiver background task"); + return err; + } + } + + /* Create a new archive group. */ + if (m_current_group == nullptr) + { + ut_ad(!attach_to_current); + + m_last_pos.init(); + m_flush_pos.init(); + m_write_pos.init(); + m_reset_pos.init(); + m_request_flush_pos.init(); + m_request_blk_num_with_lsn= std::numeric_limits::max(); + m_flush_blk_num_with_lsn= std::numeric_limits::max(); + + m_last_lsn= log_sys_lsn; + m_last_reset_file_index= 0; + + m_current_group= + UT_NEW(Arch_Group(0, log_sys_lsn, ARCH_PAGE_FILE_HDR_SIZE, + &m_mutex), mem_key_archive); + if (m_current_group == nullptr) + { + acquired_oper_mutex= false; + arch_oper_mutex_exit(); + arch_mutex_exit(); + + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(Arch_Group)); + return ER_OUTOFMEMORY; + } + + const uint64_t new_file_size= + static_cast(ARCH_PAGE_BLK_SIZE) * ARCH_PAGE_FILE_CAPACITY; + + /* Initialize archiver file context. */ + auto db_err= m_current_group->init_file_ctx( + ARCH_DIR, ARCH_PAGE_DIR, ARCH_PAGE_FILE, 0, new_file_size, 0); + + if (db_err != DB_SUCCESS) + { + arch_oper_mutex_exit(); + arch_mutex_exit(); + + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(Arch_File_Ctx)); + return ER_OUTOFMEMORY; + } + + m_group_list.push_back(m_current_group); + Arch_Block *reset_block= m_data.get_block(&m_reset_pos, ARCH_RESET_BLOCK); + reset_block->begin_write(m_write_pos); + + DBUG_PRINT("page_archiver", ("Creating a new archived group.")); + + } + else if (!attach_to_current && !recovery) + { + /* It's a reset. */ + m_last_lsn= log_sys_lsn; + m_last_pos= m_write_pos; + DBUG_PRINT("page_archiver", ("It's a reset.")); + } + + m_state= ARCH_STATE_ACTIVE; + *start_lsn= m_last_lsn; + + bool wait_for_block_flush= false; + + if (!recovery) + { + if (!attach_to_current) + wait_for_block_flush= save_reset_point(is_durable); + + else if (is_durable && !m_current_group->is_durable()) + { + /* In case this is the first durable archiving of the group and if the + gap is small for a reset then set the below variable and wait for the + reset info to be flushed before we return to the caller. */ + + wait_for_block_flush= true; + m_request_blk_num_with_lsn= m_last_pos.m_block_num; + } + } + acquired_oper_mutex= false; + arch_oper_mutex_exit(); + + ut_ad(m_last_lsn != LSN_MAX); + ut_ad(m_current_group != nullptr); + + if (!restart) + { + /* Add pages to tracking for which IO has already started. */ + track_initial_pages(); + + *group= m_current_group; + *start_pos= m_last_pos; + + arch_oper_mutex_enter(); + acquired_oper_mutex= true; + + /* Attach to the group. */ + m_current_group->attach(is_durable); + + } + else if (recovery) + { + arch_oper_mutex_enter(); + acquired_oper_mutex= true; + + /* Attach to the group. */ + m_current_group->attach(is_durable); + } + + ut_ad(*group == m_current_group); + + if (acquired_oper_mutex) + arch_oper_mutex_exit(); + + arch_mutex_exit(); + + if (wait_for_block_flush) + { + bool success= wait_for_reset_info_flush(m_request_blk_num_with_lsn); + + if (!success) + { + const char* mesg= my_get_err_msg(ER_IB_WRN_PAGE_ARCH_FLUSH_DATA); + sql_print_warning("%s", mesg); + } + ut_ad(m_current_group->get_file_count()); + } + + if (!recovery) + { + if (is_durable && !restart) + { + m_current_group->mark_active(); + m_current_group->mark_durable(); + } + /* Request checkpoint */ + log_make_checkpoint(); + } + return 0; +} + +int Arch_Page_Sys::stop(Arch_Group *group, lsn_t *stop_lsn, + Arch_Page_Pos *stop_pos, bool is_durable) +{ + Arch_Block *cur_blk; + arch_mutex_enter(); + + if (m_state == ARCH_STATE_READ_ONLY) + { + arch_mutex_exit(); + return 0; + } + ut_ad(group == m_current_group); + ut_ad(m_state == ARCH_STATE_ACTIVE); + + arch_oper_mutex_enter(); + + *stop_lsn= m_latest_stop_lsn; + cur_blk= m_data.get_block(&m_write_pos, ARCH_DATA_BLOCK); + update_stop_info(cur_blk); + + auto count= group->detach(*stop_lsn, &m_write_pos); + arch_oper_mutex_exit(); + + int err= 0; + bool wait_for_block_flush= false; + + /* If no other active client, let the system get into idle state. */ + if (count == 0 && m_state != ARCH_STATE_ABORT) + { + set_tracking_buf_pool(LSN_MAX); + arch_oper_mutex_enter(); + + m_state= ARCH_STATE_PREPARE_IDLE; + *stop_pos= m_write_pos; + + cur_blk->end_write(); + m_request_flush_pos= m_write_pos; + m_write_pos.set_next(); + + arch_sys->signal_archiver(); + wait_for_block_flush= m_current_group->is_durable() ? true : false; + + } + else + { + if (m_state != ARCH_STATE_ABORT && is_durable && + !m_current_group->is_durable_client_active()) + /* In case the non-durable clients are still active but there are no + active durable clients we need to mark the group inactive for recovery + to know that no durable clients were active. */ + err = m_current_group->mark_inactive(); + + arch_oper_mutex_enter(); + *stop_pos= m_write_pos; + } + + if (m_state == ARCH_STATE_ABORT) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err= ER_QUERY_INTERRUPTED; + } + + arch_oper_mutex_exit(); + arch_mutex_exit(); + + if (wait_for_block_flush) + { + /* Wait for flush archiver to flush the blocks. */ + auto cbk= [&]() + { + return (m_flush_pos.m_block_num > m_request_flush_pos.m_block_num ? false + : true); + }; + arch_oper_mutex_enter(); + + if (!wait_flush_archiver(cbk)) + { + const char* mesg= my_get_err_msg(ER_IB_WRN_PAGE_ARCH_FLUSH_DATA); + sql_print_warning("%s", mesg); + } + arch_oper_mutex_exit(); + ut_ad(group->validate_info_in_files()); + } + return err; +} + +void Arch_Page_Sys::release(Arch_Group *group, bool is_durable, + Arch_Page_Pos start_pos [[maybe_unused]]) +{ + arch_mutex_enter(); + arch_oper_mutex_enter(); + + group->release(is_durable); + arch_oper_mutex_exit(); + + if (group->is_active()) + { + arch_mutex_exit(); + return; + } + + ut_ad(group != m_current_group); + + if (!group->is_referenced()) + { + m_group_list.remove(group); + UT_DELETE(group); + } + arch_mutex_exit(); +} + +dberr_t Arch_Page_Sys::flush_inactive_blocks(Arch_Page_Pos &cur_pos, + Arch_Page_Pos end_pos) +{ + dberr_t err= DB_SUCCESS; + Arch_Block *cur_blk; + + /* Write all blocks that are ready for flushing. */ + while (cur_pos.m_block_num < end_pos.m_block_num) + { + cur_blk= m_data.get_block(&cur_pos, ARCH_DATA_BLOCK); + + err= cur_blk->flush(m_current_group, ARCH_FLUSH_NORMAL); + if (err != DB_SUCCESS) + break; + + MONITOR_INC(MONITOR_PAGE_TRACK_FULL_BLOCK_WRITES); + + arch_oper_mutex_enter(); + + m_flush_blk_num_with_lsn= cur_pos.m_block_num; + cur_pos.set_next(); + cur_blk->set_flushed(); + m_flush_pos.set_next(); + + arch_oper_mutex_exit(); + } + return err; +} + +dberr_t Arch_Page_Sys::flush_active_block(Arch_Page_Pos cur_pos, + bool partial_reset_block_flush) +{ + Arch_Block *cur_blk; + cur_blk= m_data.get_block(&cur_pos, ARCH_DATA_BLOCK); + + arch_oper_mutex_enter(); + + if (!cur_blk->is_active()) + { + arch_oper_mutex_exit(); + return DB_SUCCESS; + } + + /* Copy block data so that we can release the arch_oper_mutex soon. */ + Arch_Block *flush_blk= m_data.get_partial_flush_block(); + flush_blk->copy_data(cur_blk); + + arch_oper_mutex_exit(); + + dberr_t err= flush_blk->flush(m_current_group, ARCH_FLUSH_PARTIAL); + if (err != DB_SUCCESS) + return (err); + + MONITOR_INC(MONITOR_PAGE_TRACK_PARTIAL_BLOCK_WRITES); + + if (partial_reset_block_flush) + { + arch_oper_mutex_enter(); + Arch_Block *reset_block= m_data.get_block(&m_reset_pos, ARCH_RESET_BLOCK); + + arch_oper_mutex_exit(); + + err= reset_block->flush(m_current_group, ARCH_FLUSH_NORMAL); + if (err != DB_SUCCESS) + return err; + } + + arch_oper_mutex_enter(); + + m_flush_pos.m_offset= + flush_blk->get_data_len() + ARCH_PAGE_BLK_HEADER_LENGTH; + + arch_oper_mutex_exit(); + return err; +} + +dberr_t Arch_Page_Sys::flush_blocks(bool *wait) +{ + arch_oper_mutex_enter(); + + auto request_flush_pos= m_request_flush_pos; + auto cur_pos= m_flush_pos; + auto end_pos= m_write_pos; + auto request_blk_num_with_lsn= m_request_blk_num_with_lsn; + auto flush_blk_num_with_lsn= m_flush_blk_num_with_lsn; + + arch_oper_mutex_exit(); + + uint64_t ARCH_UNKNOWN_BLOCK= std::numeric_limits::max(); + + ut_ad(cur_pos.m_block_num <= end_pos.m_block_num); + + /* Caller needs to wait/sleep, if nothing to flush. */ + *wait = (cur_pos.m_block_num == end_pos.m_block_num); + + auto err = flush_inactive_blocks(cur_pos, end_pos); + if (err != DB_SUCCESS) + return err; + + if (cur_pos.m_block_num == end_pos.m_block_num) + { + /* Partial Flush */ + bool data_block_flush= + request_flush_pos.m_block_num == cur_pos.m_block_num && + request_flush_pos.m_offset > cur_pos.m_offset; + bool reset_block_flush= + request_blk_num_with_lsn != ARCH_UNKNOWN_BLOCK && + (flush_blk_num_with_lsn == ARCH_UNKNOWN_BLOCK || + request_blk_num_with_lsn > flush_blk_num_with_lsn); + + /* We do partial flush only if we're explicitly requested to flush. */ + if (data_block_flush || reset_block_flush) + { + err= flush_active_block(cur_pos, reset_block_flush); + if (err != DB_SUCCESS) + return err; + } + arch_oper_mutex_enter(); + + if (request_blk_num_with_lsn != ARCH_UNKNOWN_BLOCK && + (flush_blk_num_with_lsn == ARCH_UNKNOWN_BLOCK || + request_blk_num_with_lsn > flush_blk_num_with_lsn)) + m_flush_blk_num_with_lsn = request_blk_num_with_lsn; + + arch_oper_mutex_exit(); + } + return err; +} + +bool Arch_Page_Sys::archive(bool *wait) +{ + dberr_t db_err; + + auto is_abort= (srv_shutdown_state.load() == SRV_SHUTDOWN_LAST_PHASE || + srv_shutdown_state.load() == SRV_SHUTDOWN_EXIT_THREADS || + m_state == ARCH_STATE_ABORT); + + arch_oper_mutex_enter(); + + /* Check if archiving state is inactive. */ + if (m_state == ARCH_STATE_IDLE || m_state == ARCH_STATE_INIT) + { + *wait= true; + + if (is_abort) + { + m_state = ARCH_STATE_ABORT; + arch_oper_mutex_exit(); + return true; + } + arch_oper_mutex_exit(); + return false; + } + + /* ARCH_STATE_ABORT is set for flush timeout which is asserted in debug. */ + ut_ad(m_state == ARCH_STATE_ACTIVE || m_state == ARCH_STATE_PREPARE_IDLE); + + auto set_idle= (m_state == ARCH_STATE_PREPARE_IDLE); + arch_oper_mutex_exit(); + + db_err= flush_blocks(wait); + + if (db_err != DB_SUCCESS) + is_abort= true; + + /* Move to idle state or abort, if needed. */ + if (set_idle || is_abort) + { + arch_mutex_enter(); + arch_oper_mutex_enter(); + + m_current_group->disable(LSN_MAX); + m_current_group->close_file_ctxs(); + + int err= 0; + + if (!is_abort && m_current_group->is_durable()) + { + err= m_current_group->mark_inactive(); + + Arch_Group::init_dblwr_file_ctx( + ARCH_DBLWR_DIR, ARCH_DBLWR_FILE, ARCH_DBLWR_NUM_FILES, + static_cast(ARCH_PAGE_BLK_SIZE) * ARCH_DBLWR_FILE_CAPACITY); + + ut_ad(m_current_group->validate_info_in_files()); + } + if (err != 0) + is_abort= true; + + /* Cleanup group, if no reference. */ + if (!m_current_group->is_referenced()) + { + m_group_list.remove(m_current_group); + UT_DELETE(m_current_group); + } + + m_current_group= nullptr; + m_state= is_abort ? ARCH_STATE_ABORT : ARCH_STATE_IDLE; + + arch_oper_mutex_exit(); + arch_mutex_exit(); + } + return is_abort; +} + +int Arch_Group::read_from_file(Arch_Page_Pos *read_pos, uint read_len, + byte *read_buff) +{ + char errbuf[MYSYS_STRERROR_SIZE]; + char file_name[MAX_ARCH_PAGE_FILE_NAME_LEN]; + + /* Build file name */ + auto file_index= static_cast( + Arch_Block::get_file_index(read_pos->m_block_num, ARCH_DATA_BLOCK)); + + get_file_name(file_index, file_name, MAX_ARCH_PAGE_FILE_NAME_LEN); + + /* Find offset to read from. */ + os_offset_t offset= + Arch_Block::get_file_offset(read_pos->m_block_num, ARCH_DATA_BLOCK); + offset+= read_pos->m_offset; + + bool success; + /* Open file in read only mode. */ + pfs_os_file_t file= + os_file_create(innodb_arch_file_key, file_name, OS_FILE_OPEN, + OS_CLONE_LOG_FILE, true, &success); + + if (!success) + { + my_error(ER_CANT_OPEN_FILE, MYF(0), file_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return ER_CANT_OPEN_FILE; + } + + /* Read from file to the user buffer. */ + auto db_err= + os_file_read(IORequestRead, file, read_buff, offset, read_len, nullptr); + + os_file_close(file); + + if (db_err != DB_SUCCESS) + { + my_error(ER_ERROR_ON_READ, MYF(0), file_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return ER_ERROR_ON_READ; + } + return 0; +} + +int Arch_Group::read_data(Arch_Page_Pos cur_pos, byte *buff, uint buff_len) +{ + int err= 0; + + /* Attempt to read from in memory buffer. */ + auto success= arch_sys->page_sys()->get_pages(this, &cur_pos, buff_len, + buff); + if (!success) + /* The buffer is overwritten. Read from file. */ + err= read_from_file(&cur_pos, buff_len, buff); + + return err; +} + +bool Arch_Page_Sys::save_reset_point(bool is_durable) +{ + /* 1. Add the reset info to the reset block */ + uint current_file_index= + Arch_Block::get_file_index(m_last_pos.m_block_num, ARCH_DATA_BLOCK); + + auto reset_block= m_data.get_block(&m_reset_pos, ARCH_RESET_BLOCK); + + /* If the reset info should belong to a new file then re-intialize the + block as the block from now on will contain reset information belonging + to the new file */ + if (m_last_reset_file_index != current_file_index) + { + ut_ad(current_file_index > m_last_reset_file_index); + reset_block->begin_write(m_last_pos); + } + m_last_reset_file_index= current_file_index; + reset_block->add_reset(m_last_lsn, m_last_pos); + m_current_group->save_reset_point_in_mem(m_last_lsn, m_last_pos); + + auto cur_block= m_data.get_block(&m_last_pos, ARCH_DATA_BLOCK); + + if (cur_block->get_state() == ARCH_BLOCK_INIT || + cur_block->get_state() == ARCH_BLOCK_FLUSHED) + cur_block->begin_write(m_last_pos); + + m_latest_stop_lsn= + log_sys.last_checkpoint_lsn.load(std::memory_order_seq_cst); + update_stop_info(cur_block); + + /* 2. Add the reset lsn to the current write_pos block header and request the + flush archiver to flush the data block and reset block */ + cur_block->update_block_header(LSN_MAX, m_last_lsn); + ut_d(auto ARCH_UNKNOWN_BLOCK = std::numeric_limits::max()); + + /* Reset LSN for a block can be updated only once. */ + ut_ad(m_flush_blk_num_with_lsn == ARCH_UNKNOWN_BLOCK || + m_flush_blk_num_with_lsn < cur_block->get_number()); + ut_ad(m_request_blk_num_with_lsn == ARCH_UNKNOWN_BLOCK || + m_request_blk_num_with_lsn < cur_block->get_number()); + + uint64_t request_blk_num_with_lsn = cur_block->get_number(); + m_request_blk_num_with_lsn= request_blk_num_with_lsn; + + DBUG_PRINT("page_archiver", + ("Saved reset point at %u - %" PRIu64 ", %d , %u\n", + m_last_reset_file_index, m_last_lsn, m_last_pos.m_block_num, + m_last_pos.m_offset)); + + return is_durable; +} + +bool Arch_Page_Sys::wait_for_reset_info_flush(uint64_t request_blk) +{ + auto ARCH_UNKNOWN_BLOCK = std::numeric_limits::max(); + + auto cbk = [&]() + { + return (m_flush_blk_num_with_lsn == ARCH_UNKNOWN_BLOCK || + request_blk > m_flush_blk_num_with_lsn); + }; + + arch_oper_mutex_enter(); + bool success= wait_flush_archiver(cbk); + arch_oper_mutex_exit(); + + return success; +} + +int Arch_Page_Sys::fetch_group_within_lsn_range(lsn_t &start_id, lsn_t &stop_id, + Arch_Group **group) +{ + mysql_mutex_assert_owner(&m_mutex); + + if (start_id != 0 && stop_id != 0 && start_id >= stop_id) + return ER_PAGE_TRACKING_RANGE_NOT_TRACKED; + + arch_oper_mutex_enter(); + auto latest_stop_lsn= m_latest_stop_lsn; + arch_oper_mutex_exit(); + + ut_ad(latest_stop_lsn != LSN_MAX); + + if (start_id == 0 || stop_id == 0) + { + if (m_current_group == nullptr || !m_current_group->is_active()) + return ER_PAGE_TRACKING_RANGE_NOT_TRACKED; + + *group= m_current_group; + ut_ad(m_last_lsn != LSN_MAX); + + start_id= (start_id == 0) ? m_last_lsn : start_id; + stop_id= (stop_id == 0) ? latest_stop_lsn : stop_id; + } + + if (start_id >= stop_id || start_id == LSN_MAX || stop_id == LSN_MAX) + return ER_PAGE_TRACKING_RANGE_NOT_TRACKED; + + if (*group == nullptr) + { + for (auto it : m_group_list) + { + *group= it; + if (start_id < (*group)->get_begin_lsn() || + (!(*group)->is_active() && stop_id > (*group)->get_end_lsn()) || + ((*group)->is_active() && stop_id > latest_stop_lsn)) + { + *group= nullptr; + continue; + } + break; + } + } + return (*group) ? 0 : ER_PAGE_TRACKING_RANGE_NOT_TRACKED; +} + +uint Arch_Page_Sys::purge(lsn_t *purge_lsn) +{ + lsn_t purged_lsn= LSN_MAX; + uint err= 0; + + if (*purge_lsn == 0) + *purge_lsn = log_sys.last_checkpoint_lsn.load(std::memory_order_seq_cst); + + DBUG_PRINT("page_archiver", ("Purging of files - %" PRIu64 "", *purge_lsn)); + arch_mutex_enter(); + + for (auto it = m_group_list.begin(); it != m_group_list.end();) + { + lsn_t group_purged_lsn= LSN_MAX; + auto group= *it; + DBUG_PRINT("page_archiver", + ("End lsn - %" PRIu64 "", group->get_end_lsn())); + + err= group->purge(*purge_lsn, group_purged_lsn); + + if (group_purged_lsn == LSN_MAX) + break; + + DBUG_PRINT("page_archiver", + ("Group purged lsn - %" PRIu64 "", group_purged_lsn)); + + if (purged_lsn == LSN_MAX || group_purged_lsn > purged_lsn) + purged_lsn= group_purged_lsn; + + if (!group->is_active() && group->get_end_lsn() <= group_purged_lsn) + { + it= m_group_list.erase(it); + UT_DELETE(group); + + DBUG_PRINT("page_archiver", ("Purged entire group.")); + continue; + } + ++it; + } + DBUG_PRINT("page_archiver", + ("Purged archived file until : %" PRIu64 "", purged_lsn)); + *purge_lsn= purged_lsn; + + if (purged_lsn == LSN_MAX) + { + arch_mutex_exit(); + return err; + } + m_latest_purged_lsn = purged_lsn; + arch_mutex_exit(); + + return err; +} + +void Arch_Page_Sys::update_stop_info(Arch_Block *cur_blk) +{ + mysql_mutex_assert_owner(&m_oper_mutex); + + if (cur_blk != nullptr) + cur_blk->update_block_header(m_latest_stop_lsn, LSN_MAX); + + if (m_current_group != nullptr) + m_current_group->update_stop_point(m_write_pos, m_latest_stop_lsn); +} + +#ifdef UNIV_DEBUG +void Arch_Page_Sys::print() +{ + DBUG_PRINT("page_archiver", ("State : %u", m_state)); + DBUG_PRINT("page_archiver", ("Last pos : %d , %u", + m_last_pos.m_block_num, m_last_pos.m_offset)); + DBUG_PRINT("page_archiver", ("Last lsn : %" PRIu64 "", m_last_lsn)); + DBUG_PRINT("page_archiver", + ("Latest stop lsn : %" PRIu64 "", m_latest_stop_lsn)); + DBUG_PRINT("page_archiver", ("Flush pos : %d , %u", + m_flush_pos.m_block_num, m_flush_pos.m_offset)); + DBUG_PRINT("page_archiver", ("Write pos : %d , %u", + m_write_pos.m_block_num, m_write_pos.m_offset)); + DBUG_PRINT("page_archiver", ("Reset pos : %d , %u", + m_reset_pos.m_block_num, m_reset_pos.m_offset)); + DBUG_PRINT("page_archiver", + ("Last reset file index : %u", m_last_reset_file_index)); + + DBUG_PRINT("page_archiver", ("Latest reset block data len: %u", + (m_data.get_block(&m_reset_pos, ARCH_RESET_BLOCK))->get_data_len())); + + DBUG_PRINT("page_archiver", ("Latest data block data len: %u", + (m_data.get_block(&m_write_pos, ARCH_DATA_BLOCK))->get_data_len())); +} +#endif diff --git a/storage/innobase/arch/arch0recv.cc b/storage/innobase/arch/arch0recv.cc new file mode 100644 index 0000000000000..81fb7eb011bd0 --- /dev/null +++ b/storage/innobase/arch/arch0recv.cc @@ -0,0 +1,775 @@ +/***************************************************************************** + + +Copyright (c) 2018, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 2.0, +as published by the Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/**************************************************/ /** + @file include/arch0recv.h + Interface for crash recovery for page archiver system. + + *******************************************************/ + +#include "arch0recv.h" +#include "sql_class.h" + +dberr_t Arch_Page_Sys::recover() +{ + DBUG_PRINT("page_archiver", ("Crash Recovery")); + + Recovery arch_recv(this, ARCH_DIR); + + auto err= arch_recv.init_dblwr(); + + /* Tolerate non-existent file error. */ + if (err != DB_SUCCESS && err != DB_CANNOT_OPEN_FILE) + sql_print_error("Page Archiver's doublewrite initialisation failed"); + + /* Scan for group directories and files */ + if (!arch_recv.scan_for_groups()) + { + DBUG_PRINT("page_archiver", ("No group information available")); + return DB_SUCCESS; + } + + err= arch_recv.recover(); + + if (err != DB_SUCCESS) + { + const char* mesg= my_get_err_msg(ER_IB_ERR_PAGE_ARCH_RECOVERY_FAILED); + sql_print_error("%s", mesg); + return err; + } + err= arch_recv.load_archiver(); + + if (err != DB_SUCCESS) + { + const char* mesg= my_get_err_msg(ER_IB_ERR_PAGE_ARCH_RECOVERY_FAILED); + sql_print_error("%s", mesg); + } + return err; +} + +dberr_t Arch_Page_Sys::Recovery::init_dblwr() +{ + auto err= m_dblwr_ctx.init( + ARCH_DBLWR_DIR, ARCH_DBLWR_FILE, ARCH_DBLWR_NUM_FILES, + static_cast(ARCH_PAGE_BLK_SIZE) * ARCH_DBLWR_FILE_CAPACITY); + + if (err == DB_SUCCESS) + err= m_dblwr_ctx.read_file(); + + return err; +} + +dberr_t Arch_Dblwr_Ctx::init(const char *dblwr_path, + const char *dblwr_base_file, uint dblwr_num_files, + uint64_t dblwr_file_size) +{ + m_file_size= dblwr_file_size; + + m_buf= static_cast(ut_zalloc_nokey(static_cast(m_file_size))); + + if (m_buf == nullptr) + return DB_OUT_OF_MEMORY; + + auto err= + m_file_ctx.init(ARCH_DIR, dblwr_path, dblwr_base_file, dblwr_num_files); + + return err; +} + +dberr_t Arch_Dblwr_Ctx::read_file() +{ + auto err= m_file_ctx.open(true, LSN_MAX, 0, 0, m_file_size); + + if (err != DB_SUCCESS) + return err; + + if (m_file_ctx.get_phy_size() < m_file_size) + return DB_ERROR; + + ut_ad(m_buf != nullptr); + + /* Read the entire file. */ + err= m_file_ctx.read(m_buf, 0, static_cast(m_file_size)); + + return err; +} + +void Arch_Dblwr_Ctx::validate_and_fill_blocks(size_t num_files) +{ + auto ARCH_UNKNOWN_BLOCK= std::numeric_limits::max(); + uint64_t full_flush_blk_num= ARCH_UNKNOWN_BLOCK; + + for (uint dblwr_block_num = 0; + dblwr_block_num < m_file_size / ARCH_PAGE_BLK_SIZE; ++dblwr_block_num) + { + auto dblwr_block_offset= m_buf + (dblwr_block_num * ARCH_PAGE_BLK_SIZE); + auto block_num= Arch_Block::get_block_number(dblwr_block_offset); + uint file_index= Arch_Block::get_file_index( + block_num, Arch_Block::get_type(dblwr_block_offset)); + + ut_ad(file_index < num_files); + + /* If the block does not belong to the last file then ignore. */ + if (file_index != num_files - 1) + continue; + + if (!Arch_Block::validate(dblwr_block_offset)) + continue; + + Arch_Dblwr_Block dblwr_block; + + switch (dblwr_block_num) + { + case ARCH_PAGE_DBLWR_RESET_PAGE: + dblwr_block.m_block_type= ARCH_RESET_BLOCK; + dblwr_block.m_flush_type= ARCH_FLUSH_NORMAL; + break; + + case ARCH_PAGE_DBLWR_FULL_FLUSH_PAGE: + full_flush_blk_num= block_num; + + dblwr_block.m_block_type= ARCH_DATA_BLOCK; + dblwr_block.m_flush_type= ARCH_FLUSH_NORMAL; + break; + + case ARCH_PAGE_DBLWR_PARTIAL_FLUSH_PAGE: + /* It's possible that the partial flush block might have been fully + flushed, in which case we need to skip this block. */ + if (full_flush_blk_num != ARCH_UNKNOWN_BLOCK && + full_flush_blk_num >= block_num) + continue; + + dblwr_block.m_block_type= ARCH_DATA_BLOCK; + dblwr_block.m_flush_type= ARCH_FLUSH_PARTIAL; + break; + + default: + ut_d(ut_error); + } + + dblwr_block.m_block= dblwr_block_offset; + dblwr_block.m_block_num= static_cast(block_num); + + m_blocks.push_back(dblwr_block); + } +} + +#ifdef UNIV_DEBUG +void Arch_Page_Sys::Recovery::print() +{ + for (auto group= m_dir_group_info_map.begin(); + group != m_dir_group_info_map.end(); ++group) + DBUG_PRINT("page_archiver", ("Group : %s\t%u", group->first.c_str(), + group->second.m_active)); +} +#endif + +void Arch_Page_Sys::Recovery::read_group_dirs(const std::string file_path) +{ + if (file_path.find(ARCH_PAGE_DIR) == std::string::npos) + return; + + try + { + size_t pos= file_path.find(ARCH_PAGE_DIR); + + lsn_t start_lsn= static_cast( + std::stoull(file_path.substr(pos + strlen(ARCH_PAGE_DIR)))); + + auto &group_info= m_dir_group_info_map[file_path]; + + group_info.m_start_lsn= start_lsn; + + } + catch (const std::exception &) + { + const char* format= my_get_err_msg(ER_IB_ERR_PAGE_ARCH_INVALID_FORMAT); + my_printf_error(ER_IB_ERR_PAGE_ARCH_INVALID_FORMAT, format, + MYF(ME_ERROR_LOG_ONLY), ARCH_PAGE_FILE); + return; + } +} + +void Arch_Page_Sys::Recovery::read_group_files(const std::string dir_path, + const std::string file_path) +{ + if (file_path.find(ARCH_PAGE_FILE) == std::string::npos && + file_path.find(ARCH_PAGE_GROUP_ACTIVE_FILE_NAME) == std::string::npos && + file_path.find(ARCH_PAGE_GROUP_DURABLE_FILE_NAME) == std::string::npos) + return; + + auto &info= m_dir_group_info_map[dir_path]; + + if (file_path.find(ARCH_PAGE_GROUP_ACTIVE_FILE_NAME) != std::string::npos) + { + info.m_active = true; + return; + } + + if (file_path.find(ARCH_PAGE_GROUP_DURABLE_FILE_NAME) != std::string::npos) + { + info.m_durable = true; + return; + } + + info.m_num_files+= 1; + + /* Fetch start index. */ + try + { + size_t found= file_path.find(ARCH_PAGE_FILE); + + auto file_index= static_cast( + std::stoi(file_path.substr(found + strlen(ARCH_PAGE_FILE)))); + + if (info.m_file_start_index > file_index) + info.m_file_start_index = file_index; + } + catch (const std::exception &) + { + const char* format= my_get_err_msg(ER_IB_ERR_PAGE_ARCH_INVALID_FORMAT); + my_printf_error(ER_IB_ERR_PAGE_ARCH_INVALID_FORMAT, format, + MYF(ME_ERROR_LOG_ONLY), ARCH_PAGE_FILE); + return; + } +} + +bool Arch_Page_Sys::Recovery::scan_for_groups() +{ + os_file_type_t type; + bool exists; + + os_file_status(m_arch_dir_name.c_str(), &exists, &type); + + if (!exists || type != OS_FILE_TYPE_DIR) + return false; + + auto read_directory_fn= [&](const char *file_path, const char *file_name) + { + char path[MAX_ARCH_PAGE_FILE_NAME_LEN]; + snprintf(path, sizeof(path), "%s%c%s", file_path, OS_PATH_SEPARATOR, + file_name); + read_group_dirs(path); + }; + os_file_scan_directory(m_arch_dir_name.c_str(), read_directory_fn, false); + + if (m_dir_group_info_map.size() == 0) + return false; + + for (auto it = m_dir_group_info_map.begin(); it != m_dir_group_info_map.end(); + ++it) + { + auto read_files_fn= [&](const char *dir_path, const char *file_path) + { + read_group_files(dir_path, file_path); + }; + os_file_scan_directory(it->first.c_str(), read_files_fn, false); + } + ut_d(print()); + return true; +} + +dberr_t Arch_Group::Recovery::replace_pages_from_dblwr( + Arch_Dblwr_Ctx *dblwr_ctx) +{ + dberr_t err{DB_SUCCESS}; + + uint num_files= m_group->get_file_count(); + + ut_ad(num_files); + + dblwr_ctx->validate_and_fill_blocks(num_files); + + auto &file_ctx= m_group->m_file_ctx; + + err= file_ctx.open(false, m_group->m_begin_lsn, num_files - 1, 0, + m_group->get_file_size()); + + if (err != DB_SUCCESS) + return err; + + Arch_scope_guard file_ctx_guard([&file_ctx] { file_ctx.close(); }); + + auto dblwr_blocks= dblwr_ctx->blocks(); + + for (uint index= 0; index < dblwr_blocks.size(); ++index) + { + auto dblwr_block= dblwr_blocks[index]; + uint64_t offset= Arch_Block::get_file_offset( + dblwr_block.m_block_num, Arch_Block::get_type(dblwr_block.m_block)); + + if (file_ctx.get_phy_size() < offset) + break; + + err= file_ctx.write(nullptr, dblwr_block.m_block, + static_cast(offset), ARCH_PAGE_BLK_SIZE); + + if (err != DB_SUCCESS) + break; + } + + return err; +} + +dberr_t Arch_Group::Recovery::cleanup_if_required(Arch_Recv_Group_Info &info) +{ + ut_ad(!info.m_durable || info.m_num_files > 0); + + auto &file_ctx= m_group->m_file_ctx; + auto start_index= info.m_file_start_index; + uint index= start_index + info.m_num_files - 1; + + ut_ad(file_ctx.is_closed()); + + /* Open the last file in the group. */ + auto err= file_ctx.open(true, m_group->m_begin_lsn, index, 0, + m_group->get_file_size()); + + if (err != DB_SUCCESS) + return err; + + Arch_scope_guard file_ctx_guard([&file_ctx] { file_ctx.close(); }); + + /* We check whether the archive file has anything else apart from the header + * that was written to it during creation phase and treat it as an empty file + * if it only has the header. */ + + if (file_ctx.get_phy_size() > m_group->m_header_len && info.m_durable) + return DB_SUCCESS; + + info.m_new_empty_file= true; + + /* No blocks have been flushed into the file so delete the file. */ + + char file_path[MAX_ARCH_PAGE_FILE_NAME_LEN]; + char dir_path[MAX_ARCH_DIR_NAME_LEN]; + + file_ctx.build_name(index, m_group->m_begin_lsn, file_path, + MAX_ARCH_PAGE_FILE_NAME_LEN); + + auto found= std::string(file_path).find(ARCH_PAGE_FILE); + ut_ad(found != std::string::npos); + auto file_name= std::string(file_path).substr(found); + + file_ctx.build_dir_name(m_group->m_begin_lsn, dir_path, + MAX_ARCH_DIR_NAME_LEN); + + file_ctx_guard.cleanup(); + + Arch_Sys::remove_file(dir_path, file_name.c_str()); + + --info.m_num_files; + + /* If there are no archive files in the group or if it's not a durable group + we might as well purge it. */ + if (info.m_num_files == 0 || !info.m_durable) + { + m_group->m_is_active= false; + + found= std::string(dir_path).find(ARCH_PAGE_DIR); + ut_ad(found != std::string::npos); + + auto path= std::string(dir_path).substr(0, found - 1); + auto dir_name= std::string(dir_path).substr(found); + + info.m_num_files= 0; + Arch_Sys::remove_dir(path.c_str(), dir_name.c_str()); + + return err; + } + + /* Need to reinitialize the file context as num_files has changed. */ + err= + file_ctx.init(ARCH_DIR, ARCH_PAGE_DIR, ARCH_PAGE_FILE, info.m_num_files); + + return err; +} + +dberr_t Arch_Page_Sys::Recovery::recover() +{ + dberr_t err= DB_SUCCESS; + uint num_active [[maybe_unused]]= 0; + + for (auto info = m_dir_group_info_map.begin(); + info != m_dir_group_info_map.end(); ++info) + { + auto &group_info= info->second; + + Arch_Group *group= + UT_NEW(Arch_Group(0, group_info.m_start_lsn, + ARCH_PAGE_FILE_HDR_SIZE, m_page_sys->get_mutex()), + mem_key_archive); + + if (group == nullptr) + return DB_OUT_OF_MEMORY; + + err= group->recover(group_info, &m_dblwr_ctx); + + if (err != DB_SUCCESS) + { + group->disable(LSN_MAX); + UT_DELETE(group); + break; + } + + if (group_info.m_num_files == 0) + { + group->disable(LSN_MAX); + UT_DELETE(group); + continue; + } + + if (group_info.m_active) + ++num_active; + + group_info.m_group= group; + } + + /* There can be only one active group at a time. */ + ut_ad(num_active <= 1); + + return err; +} + +dberr_t Arch_Page_Sys::Recovery::load_archiver() +{ + dberr_t err= DB_SUCCESS; + + for (auto info_map= m_dir_group_info_map.begin(); + info_map != m_dir_group_info_map.end(); ++info_map) + { + auto &info= info_map->second; + + if (info.m_group == nullptr) + continue; + + m_page_sys->m_group_list.push_back(info.m_group); + + if (!info.m_active) + continue; + + /* Group was active at the time of shutdown/crash, start page archiving. */ + err= info.m_group->open_file(info.m_write_pos, info.m_new_empty_file); + + if (err != DB_SUCCESS) + break; + + int error= m_page_sys->recovery_load_and_start(info); + + if (error) + { + err= DB_ERROR; + break; + } + } + return err; +} + +dberr_t Arch_Group::recover(Arch_Recv_Group_Info &group_info, + Arch_Dblwr_Ctx *dblwr_ctx) +{ + Recovery group_recv(this); + + const auto file_size= + static_cast(ARCH_PAGE_BLK_SIZE) * ARCH_PAGE_FILE_CAPACITY; + + auto err= init_file_ctx(ARCH_DIR, ARCH_PAGE_DIR, ARCH_PAGE_FILE, + group_info.m_num_files, file_size, 0); + + if (err != DB_SUCCESS) + return err; + + if (group_info.m_active) + { + /* Since the group was active at the time of crash it's possible that the + doublewrite buffer might have the latest data in case of a crash. */ + err= group_recv.replace_pages_from_dblwr(dblwr_ctx); + + if (err != DB_SUCCESS) + return err; + } + + err = group_recv.cleanup_if_required(group_info); + + if (err != DB_SUCCESS || group_info.m_num_files == 0) { + return err; + } + + err= group_recv.parse(group_info); + + if (err != DB_SUCCESS) + return err; + + if (!group_info.m_active) + { + auto end_lsn= group_info.m_last_stop_lsn; + ut_ad(end_lsn != LSN_MAX); + + m_stop_pos= group_info.m_write_pos; + m_end_lsn= end_lsn; + + group_recv.attach(); + disable(end_lsn); + } + +#ifdef UNIV_DEBUG + Arch_File_Ctx::Recovery file_ctx_recv(m_file_ctx); + file_ctx_recv.reset_print(group_info.m_file_start_index); +#endif + return err; +} + +#ifdef UNIV_DEBUG +void Arch_File_Ctx::Recovery::reset_print(uint file_start_index) +{ + Arch_Reset reset; + Arch_Reset_File reset_file; + Arch_Point start_point; + + DBUG_PRINT("page_archiver", ("No. of files : %u", m_file_ctx.m_count)); + + if (m_file_ctx.m_reset.size() == 0) { + DBUG_PRINT("page_archiver", ("No reset info available for this group.")); + } + + for (auto reset_file : m_file_ctx.m_reset) + { + DBUG_PRINT("page_archiver", ("File %u\tFile LSN : %" PRIu64 "", + reset_file.m_file_index, reset_file.m_lsn)); + + if (reset_file.m_start_point.size() == 0) + DBUG_PRINT("page_archiver", ("No reset info available for this file.")); + + for (uint i= 0; i < reset_file.m_start_point.size(); i++) + { + start_point= reset_file.m_start_point[i]; + DBUG_PRINT("page_archiver", + ("\tReset lsn : %" PRIu64 ", reset_pos : %d \t %u", + start_point.lsn, start_point.pos.m_block_num, + start_point.pos.m_offset)); + } + } + DBUG_PRINT("page_archiver", + ("Starting index of the file : %u", file_start_index)); + + DBUG_PRINT("page_archiver", ("Latest stop points")); + uint file_index= 0; + for (auto stop_point : m_file_ctx.m_stop_points) + { + ut_ad(stop_point); + DBUG_PRINT("page_archiver", + ("\tFile %u : %" PRIu64 "", file_index, stop_point)); + ++file_index; + } +} +#endif + +dberr_t Arch_Group::Recovery::parse(Arch_Recv_Group_Info &info) +{ + dberr_t err= DB_SUCCESS; + + size_t num_files= m_group->get_file_count(); + + if (num_files == 0) + { + DBUG_PRINT("page_archiver", ("No group information available")); + return DB_SUCCESS; + } + + uint start_index= info.m_file_start_index; + size_t file_count= start_index + num_files; + + auto &file_ctx= m_group->m_file_ctx; + + for (uint file_index = start_index; file_index < file_count; ++file_index) + { + Arch_File_Ctx::Recovery file_ctx_recv(file_ctx); + Arch_scope_guard file_ctx_guard([&file_ctx] { file_ctx.close(); }); + + if (file_index == start_index) + err= file_ctx.open(true, m_group->m_begin_lsn, start_index, 0, + m_group->get_file_size()); + else + err= file_ctx.open_next(m_group->m_begin_lsn, 0, + m_group->get_file_size()); + + if (err != DB_SUCCESS) + break; + + bool last_file= (file_index + 1 == file_count); + + err= file_ctx_recv.parse_reset_points(file_index, last_file, info); + if (err != DB_SUCCESS) + break; + + err = file_ctx_recv.parse_stop_points(last_file, info); + if (err != DB_SUCCESS) + break; + } + return err; +} + +dberr_t Arch_File_Ctx::Recovery::parse_stop_points(bool last_file, + Arch_Recv_Group_Info &info) +{ + ut_ad(!m_file_ctx.is_closed()); + + uint64_t offset; + auto buf= std::make_unique(ARCH_PAGE_BLK_SIZE); + + auto phy_size= m_file_ctx.get_phy_size(); + + if (last_file) + offset = phy_size - ARCH_PAGE_BLK_SIZE; + else + offset = ARCH_PAGE_FILE_DATA_CAPACITY * ARCH_PAGE_BLK_SIZE; + + if (phy_size < offset + ARCH_PAGE_BLK_SIZE) + return DB_ERROR; + + auto err= m_file_ctx.read(buf.get(), offset, ARCH_PAGE_BLK_SIZE); + if (err != DB_SUCCESS) + return err; + + auto stop_lsn= Arch_Block::get_stop_lsn(buf.get()); + m_file_ctx.m_stop_points.push_back(stop_lsn); + + if (last_file) + { + info.m_last_stop_lsn= stop_lsn; + memcpy(info.m_last_data_block, buf.get(), ARCH_PAGE_BLK_SIZE); + } + + info.m_write_pos.init(); + info.m_write_pos.m_block_num= + static_cast(Arch_Block::get_block_number(buf.get())); + info.m_write_pos.m_offset= + Arch_Block::get_data_len(buf.get()) + ARCH_PAGE_BLK_HEADER_LENGTH; + + return err; +} + +dberr_t Arch_File_Ctx::Recovery::parse_reset_points( + uint file_index, bool last_file, Arch_Recv_Group_Info &info) +{ + ut_ad(!m_file_ctx.is_closed()); + ut_ad(m_file_ctx.m_index == file_index); + + auto buf= std::make_unique(ARCH_PAGE_BLK_SIZE); + + if (m_file_ctx.get_phy_size() < ARCH_PAGE_BLK_SIZE) + return DB_ERROR; + + /* Read reset block to fetch reset points. */ + auto err= m_file_ctx.read(buf.get(), 0, ARCH_PAGE_BLK_SIZE); + if (err != DB_SUCCESS) + return err; + + auto block_num= static_cast( + Arch_Block::get_block_number(buf.get())); + auto data_len= Arch_Block::get_data_len(buf.get()); + + if (file_index != block_num) + { + /* This means there was no reset for this file and hence the + reset block was not flushed. */ + ut_ad(Arch_Block::is_zeros(buf.get(), ARCH_PAGE_BLK_SIZE)); + info.m_reset_pos.init(); + info.m_reset_pos.m_block_num= file_index; + return err; + } + + /* Normal case. */ + info.m_reset_pos.m_block_num= block_num; + info.m_reset_pos.m_offset= data_len + ARCH_PAGE_BLK_HEADER_LENGTH; + + if (last_file) + memcpy(info.m_last_reset_block, buf.get(), ARCH_PAGE_BLK_SIZE); + + Arch_Reset_File reset_file; + reset_file.init(); + reset_file.m_file_index= file_index; + + if (data_len != 0) + { + uint length= 0; + byte *buf1= buf.get() + ARCH_PAGE_BLK_HEADER_LENGTH; + + ut_ad(data_len >= ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE + + ARCH_PAGE_FILE_HEADER_RESET_POS_SIZE); + + reset_file.m_lsn= mach_read_from_8(buf1); + length+= ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE; + + Arch_Point start_point; + Arch_Page_Pos pos; + + while (length != data_len) + { + ut_ad((data_len - length) % ARCH_PAGE_FILE_HEADER_RESET_POS_SIZE == 0); + + pos.m_block_num= mach_read_from_2(buf1 + length); + length+= ARCH_PAGE_FILE_HEADER_RESET_BLOCK_NUM_SIZE; + + pos.m_offset= mach_read_from_2(buf1 + length); + length+= ARCH_PAGE_FILE_HEADER_RESET_BLOCK_OFFSET_SIZE; + + start_point.lsn= m_file_ctx.fetch_reset_lsn(pos.m_block_num); + start_point.pos= pos; + + reset_file.m_start_point.push_back(start_point); + } + + m_file_ctx.m_reset.push_back(reset_file); + } + + info.m_last_reset_file= reset_file; + return err; +} + +lsn_t Arch_File_Ctx::fetch_reset_lsn(uint64_t block_num) +{ + ut_ad(!is_closed()); + ut_ad(Arch_Block::get_file_index(block_num, ARCH_DATA_BLOCK) == m_index); + + auto buf= std::make_unique(ARCH_PAGE_BLK_SIZE); + + auto offset= Arch_Block::get_file_offset(block_num, ARCH_DATA_BLOCK); + + ut_ad(offset + ARCH_PAGE_BLK_SIZE <= get_phy_size()); + + auto err= read(buf.get(), offset, ARCH_PAGE_BLK_HEADER_LENGTH); + + if (err != DB_SUCCESS) + return (LSN_MAX); + + auto lsn= Arch_Block::get_reset_lsn(buf.get()); + + ut_ad(lsn != LSN_MAX); + return lsn; +} diff --git a/storage/innobase/buf/buf0buf.cc b/storage/innobase/buf/buf0buf.cc index 5f066b408d139..87b8fad00379e 100644 --- a/storage/innobase/buf/buf0buf.cc +++ b/storage/innobase/buf/buf0buf.cc @@ -1489,6 +1489,10 @@ bool buf_pool_t::create() noexcept buf_LRU_old_ratio_update(100 * 3 / 8, false); btr_search_sys_create(); + /* Dirty Page Tracking is disabled by default. */ + track_page_lsn = LSN_MAX; + max_lsn_io = 0; + #ifdef __linux__ if (srv_operation == SRV_OPERATION_NORMAL) buf_mem_pressure_detect_init(); @@ -2808,7 +2812,7 @@ buf_page_get_gen( ignore_unfixed: ut_ad(mode == BUF_GET_POSSIBLY_FREED || mode == BUF_PEEK_IF_IN_POOL); - if (err) { + if (err && mode != BUF_GET_POSSIBLY_FREED) { *err = DB_CORRUPTION; } return nullptr; diff --git a/storage/innobase/buf/buf0dump.cc b/storage/innobase/buf/buf0dump.cc index d13e0d0c82c5a..71a03d40aa8ac 100644 --- a/storage/innobase/buf/buf0dump.cc +++ b/storage/innobase/buf/buf0dump.cc @@ -175,7 +175,7 @@ get_buf_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) +void buf_dump_generate_path(char *path, size_t path_size) { char buf[FN_REFLEN]; diff --git a/storage/innobase/buf/buf0flu.cc b/storage/innobase/buf/buf0flu.cc index 5f6d677aa8005..88b3efceb8791 100644 --- a/storage/innobase/buf/buf0flu.cc +++ b/storage/innobase/buf/buf0flu.cc @@ -30,6 +30,7 @@ Created 11/11/1995 Heikki Tuuri #include #include +#include "arch0arch.h" #include "buf0flu.h" #include "buf0lru.h" #include "buf0buf.h" @@ -389,6 +390,20 @@ void buf_flush_assign_full_crc32_checksum(byte* page) noexcept mach_write_to_4(page + payload, my_crc32c(0, page, payload)); } +bool page_is_uncompressed_type(const byte *page) +{ + switch (fil_page_get_type(page)) + { + case FIL_PAGE_TYPE_ALLOCATED: + case FIL_PAGE_INODE: + case FIL_PAGE_IBUF_BITMAP: + case FIL_PAGE_TYPE_FSP_HDR: + case FIL_PAGE_TYPE_XDES: + return true; + } + return false; +} + /** Initialize a page for writing to the tablespace. @param[in] block buffer block; NULL if bypassing the buffer pool @@ -432,6 +447,7 @@ buf_flush_init_for_writing( case FIL_PAGE_TYPE_FSP_HDR: case FIL_PAGE_TYPE_XDES: /* These are essentially uncompressed pages. */ + ut_ad(page_is_uncompressed_type(page)); memcpy(page_zip->data, page, size); /* fall through */ case FIL_PAGE_TYPE_ZBLOB: @@ -777,6 +793,17 @@ bool buf_page_t::flush(fil_space_t *space) noexcept ? oldest_modification() == 2 : oldest_modification() > 2); + if (!fsp_is_system_temporary(id().space())) + { + auto oldest_lsn= oldest_modification(); + ut_ad(oldest_lsn > 2); + buf_pool.set_max_lsn_io(oldest_lsn); + + auto [tracking, track_lsn]= buf_pool.is_tracking(); + if (tracking) + arch_sys->page_sys()->track_page(this, track_lsn, oldest_lsn, + marked_tracking()); + } /* Increment the I/O operation count used for selecting LRU policy. */ buf_LRU_stat_inc_io(); mysql_mutex_unlock(&buf_pool.mutex); @@ -1874,6 +1901,7 @@ inline void log_t::write_checkpoint(lsn_t end_lsn) noexcept next_checkpoint_no++; const lsn_t checkpoint_lsn{next_checkpoint_lsn}; last_checkpoint_lsn= checkpoint_lsn; + last_checkpoint_end_lsn= end_lsn; DBUG_PRINT("ib_log", ("checkpoint ended at " LSN_PF ", flushed to " LSN_PF, checkpoint_lsn, get_flushed_lsn())); @@ -2061,6 +2089,9 @@ static bool log_checkpoint() noexcept mysql_mutex_lock(&buf_pool.flush_list_mutex); const lsn_t oldest_lsn= buf_pool.get_oldest_modification(end_lsn); mysql_mutex_unlock(&buf_pool.flush_list_mutex); + + if (arch_sys) + arch_sys->page_sys()->flush_at_checkpoint(oldest_lsn); return log_checkpoint_low(oldest_lsn, end_lsn); } diff --git a/storage/innobase/clone/clone0api.cc b/storage/innobase/clone/clone0api.cc new file mode 100644 index 0000000000000..d28f615eb170d --- /dev/null +++ b/storage/innobase/clone/clone0api.cc @@ -0,0 +1,1898 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file clone/clone0api.cc + Innodb Clone Interface + + *******************************************************/ +#include +#include +#include + +#define MYSQL_SERVER 1 +#include "my_global.h" +#include "sql_class.h" + +#include "mysqld.h" +#include "backup.h" +#include "span.h" +#include "sql_table.h" +#include "strfunc.h" +#include "ha_innodb.h" + +#include "btr0pcur.h" +#include "clone0api.h" +#include "clone0clone.h" +#include "clone_handler.h" +#include "dict0load.h" +#include "trx0sys.h" + +extern void ignore_db_dirs_append(const char *dirname_arg); + +/** Check if clone status file exists. +@param[in] file_name file name +@return true if file exists. */ +static bool file_exists(const std::string &file_name) +{ + std::ifstream file(file_name.c_str()); + + if (file.is_open()) + { + file.close(); + return true; + } + return false; +} + +/** Rename clone status file. The operation is expected to be atomic +when the files belong to same directory. +@param[in] from_file name of current file +@param[in] to_file name of new file */ +static void rename_file(const std::string &from_file, + const std::string &to_file) +{ + auto ret= std::rename(from_file.c_str(), to_file.c_str()); + + if (ret != 0) + { + ib::fatal() + << "Error renaming file from: " << from_file.c_str() + << " to: " << to_file.c_str(); + } +} + +/** Create clone status file. +@param[in] file_name file name */ +static void create_file(std::string &file_name) +{ + std::ofstream file(file_name.c_str()); + + if (file.is_open()) + { + file.close(); + return; + } + ib::error() << "Error creating file : " << file_name.c_str(); +} + +/** Delete clone status file or directory. +@param[in] file name of file */ +static void remove_file(const std::string &file) +{ + os_file_type_t file_type; + bool exists; + + if (!os_file_status(file.c_str(), &exists, &file_type)) + { + ib::error() << "Error checking a file to remove : " << file.c_str(); + return; + } + /* Allow non existent file, as the server could have crashed or returned + with error before creating the file. This is needed during error cleanup. */ + if (!exists) + return; + + /* In C++17 there will be std::filesystem::remove_all and the + code below will no longer be required. */ + if (file_type == OS_FILE_TYPE_DIR) + { + auto scan_cbk= [](const char *path, const char *file_name) + { + if (strcmp(file_name, ".") == 0 || strcmp(file_name, "..") == 0) + return; + + const auto to_remove= std::string{path} + OS_PATH_SEPARATOR + file_name; + remove_file(to_remove); + }; + + if (!os_file_scan_directory(file.c_str(), scan_cbk, true)) + ib::error() << "Error removing directory : " << file.c_str(); + } + else + { + auto ret= std::remove(file.c_str()); + if (ret != 0) + ib::error() << "Error removing file : " << file.c_str(); + } +} + +/** Create clone in progress file and error file. +@param[in] clone clone handle */ +static void create_status_file(const Clone_Handle *clone) +{ + const char *path= clone->get_datadir(); + std::string file_name; + + if (clone->replace_datadir()) + { + /* Create error file for rollback. */ + file_name.assign(CLONE_INNODB_ERROR_FILE); + create_file(file_name); + return; + } + + file_name.assign(path); + /* Add path separator if needed. */ + if (file_name.back() != OS_PATH_SEPARATOR) + file_name.append(OS_PATH_SEPARATOR_STR); + + file_name.append(CLONE_INNODB_IN_PROGRESS_FILE); + create_file(file_name); +} + +/** Drop clone in progress file and error file. +@param[in] clone clone handle */ +static void drop_status_file(const Clone_Handle *clone) +{ + const char *path= clone->get_datadir(); + std::string file_name; + + if (clone->replace_datadir()) + { + /* Indicate that clone needs table fix up on recovery. */ + file_name.assign(CLONE_INNODB_FIXUP_FILE); + create_file(file_name); + + /* drop error file on success. */ + file_name.assign(CLONE_INNODB_ERROR_FILE); + remove_file(file_name); + + DBUG_EXECUTE_IF("clone_recovery_crash_point", + { + file_name.assign(CLONE_INNODB_RECOVERY_CRASH_POINT); + create_file(file_name); + }); + return; + } + + std::string path_name(path); + /* Add path separator if needed. */ + if (path_name.back() != OS_PATH_SEPARATOR) + path_name.append(OS_PATH_SEPARATOR_STR); + + /* Indicate that clone needs table fix up on recovery. */ + file_name.assign(path_name); + file_name.append(CLONE_INNODB_FIXUP_FILE); + create_file(file_name); + + /* Indicate clone needs to update recovery status. */ + file_name.assign(path_name); + file_name.append(CLONE_INNODB_REPLACED_FILES); + create_file(file_name); + + /* Mark successful clone operation. */ + file_name.assign(path_name); + file_name.append(CLONE_INNODB_IN_PROGRESS_FILE); + remove_file(file_name); +} + +void clone_init_list_files() +{ + /* Remove any existing list files. */ + std::string new_files(CLONE_INNODB_NEW_FILES); + remove_file(new_files); + + std::string old_files(CLONE_INNODB_OLD_FILES); + remove_file(old_files); + + std::string replaced_files(CLONE_INNODB_REPLACED_FILES); + remove_file(replaced_files); + + std::string recovery_file(CLONE_INNODB_RECOVERY_FILE); + remove_file(recovery_file); + + std::string ddl_file(CLONE_INNODB_DDL_FILES); + remove_file(ddl_file); +} + +void clone_remove_list_file(const char *file_name) +{ + std::string list_file(file_name); + remove_file(list_file); +} + +int clone_add_to_list_file(const char *list_file_name, const char *file_name) +{ + std::ofstream list_file; + list_file.open(list_file_name, std::ofstream::app); + + if (list_file.is_open()) + { + list_file << file_name << std::endl; + + if (list_file.good()) + { + list_file.close(); + return 0; + } + list_file.close(); + } + /* This is an error case. Either open or write call failed. */ + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_ERROR_ON_WRITE, MYF(0), list_file_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return ER_ERROR_ON_WRITE; +} + +/** Add redo log directory to the old file list. */ +static void track_redo_files() +{ + const auto path= get_log_file_path(); + + /* Skip the path separator which is at the end. */ + ut_ad(!path.empty()); + ut_ad(path.back() == OS_PATH_SEPARATOR); + const auto str= path.substr(0, path.size() - 1); + + clone_add_to_list_file(CLONE_INNODB_OLD_FILES, str.c_str()); +} + +/** Execute sql statement. +@param[in,out] thd current THD +@param[in] sql_stmt SQL statement +@param[in] thread_number executing thread number +@param[in] skip_error skip statement on error +@return false, if successful. */ +// static bool clone_execute_query(THD *thd, const char *sql_stmt, +// size_t thread_number, bool skip_error); + +/** Delete all binary logs before clone. +@param[in] thd current THD +@return error code */ +// static int clone_drop_binary_logs(THD *thd); + +/** Drop all user data before starting clone. +@param[in,out] thd current THD +@param[in] allow_threads allow multiple threads +@return error code */ +// static int clone_drop_user_data(THD *thd, bool allow_threads); + +/** Open all Innodb tablespaces. +@param[in,out] thd session THD +@return error code. */ +static int clone_init_tablespaces(THD *thd); + +void innodb_clone_get_capability(Ha_clone_flagset &flags) +{ + flags.reset(); + flags.set(HA_CLONE_HYBRID); + flags.set(HA_CLONE_MULTI_TASK); + flags.set(HA_CLONE_RESTART); +} + +/** Check if clone can be started. +@param[in,out] thd session THD +@return error code. */ +static int clone_begin_check(THD *thd) +{ + mysql_mutex_assert_owner(clone_sys->get_mutex()); + int err= 0; + + if (Clone_Sys::s_clone_sys_state == CLONE_SYS_ABORT) + err= ER_CLONE_DDL_IN_PROGRESS; + + if (err != 0 && thd != nullptr) + my_error(err, MYF(0)); + + return err; +} + +/** Get clone timeout configuration value. +@param[in,out] thd server thread handle +@param[in] config_name timeout configuration name +@param[out] timeout timeout value +@return true iff successful. */ +static bool get_clone_timeout_config(THD *thd, const std::string &config_name, + int &timeout) +{ + timeout= 0; + using Clone_Key_Values= std::vector>; + + /* Get timeout configuration in string format and convert to integer. + Currently there is no interface to get the integer value directly. The + variable is in clone plugin and innodb cannot access it directly. */ + Clone_Key_Values timeout_confs= {{config_name, ""}}; + auto err= clone_get_configs(thd, static_cast(&timeout_confs)); + + std::string err_str("Error reading configuration: "); + err_str.append(config_name); + + if (err != 0) + { + ib::error() << err_str; + return false; + } + + try + { + timeout = std::stoi(timeout_confs[0].second); + } + catch (const std::exception &e) + { + err_str.append(" Exception: "); + err_str.append(e.what()); + ib::error() << err_str; + ut_d(ut_error); + return false; + } + return true; +} + +#if 0 +/** Timeout while waiting for DDL commands. +@param[in,out] thd server thread handle +@return donor timeout in seconds. */ +static int get_ddl_timeout(THD *thd) +{ + int timeout= 0; + std::string config_timeout("clone_ddl_timeout"); + + if (!get_clone_timeout_config(thd, config_timeout, timeout)) + /* Default to five minutes in case error reading configuration. */ + timeout = 300; + + return timeout; +} +#endif + +int innodb_clone_begin(THD *thd, const byte *&loc, uint &loc_len, + uint &task_id, Ha_clone_type type, Ha_clone_mode mode) +{ + /* Check if reference locator is valid */ + if (loc != nullptr && !clone_validate_locator(loc, loc_len)) + { + int err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Locator"); + return err; + } + + /* Acquire clone system mutex which would automatically get released + when we return from the function [RAII]. */ + Mysql_mutex_guard sys_mutex(clone_sys->get_mutex()); + + /* Check if concurrent ddl has marked abort. */ + int err = clone_begin_check(thd); + + if (err != 0) + return err; + + /* Check of clone is already in progress for the reference locator. */ + auto clone_hdl= clone_sys->find_clone(loc, loc_len, CLONE_HDL_COPY); + + switch (mode) + { + case HA_CLONE_MODE_RESTART: + /* Error out if existing clone is not found */ + if (clone_hdl == nullptr) + { + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Restart could not find existing clone"); + return (ER_INTERNAL_ERROR); + } + ib::info() << "Clone Begin Master Task: Restart"; + err= clone_hdl->restart_copy(thd, loc, loc_len); + break; + + case HA_CLONE_MODE_START: + { + /* Should not find existing clone for the locator */ + if (clone_hdl != nullptr) + { + clone_sys->drop_clone(clone_hdl); + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Begin refers existing clone"); + return ER_INTERNAL_ERROR; + } + auto &sctx= thd->main_security_ctx; + + /* Should not become a donor when provisioning is started. */ + if (Clone_handler::is_provisioning() && sctx.host_or_ip) + { + if (0 == strcmp(my_localhost, sctx.host_or_ip)) + { + my_error(ER_CLONE_LOOPBACK, MYF(0)); + return ER_CLONE_LOOPBACK; + } + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(0), MAX_CLONES); + return ER_CLONE_TOO_MANY_CONCURRENT_CLONES; + } + + /* Log user and host beginning clone operation. */ + ib::info() << "Clone Begin Master Task by " + << sctx.user << "@" << sctx.host_or_ip; + break; + } + + case HA_CLONE_MODE_ADD_TASK: + /* Should find existing clone for the locator */ + if (clone_hdl == nullptr) + { + /* Operation has finished already */ + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone add task refers non-existing clone"); + + return ER_INTERNAL_ERROR; + } + break; + + case HA_CLONE_MODE_VERSION: + case HA_CLONE_MODE_MAX: + default: + my_error(ER_INTERNAL_ERROR, MYF(0), "Innodb Clone Begin Invalid Mode"); + + ut_d(ut_error); + return ER_INTERNAL_ERROR; + } + + if (clone_hdl == nullptr) + { + ut_ad(thd != nullptr); + ut_ad(mode == HA_CLONE_MODE_START); + + /* Create new clone handle for copy. Reference locator + is used for matching the version. */ + auto err= clone_sys->add_clone(loc, CLONE_HDL_COPY, clone_hdl); + + if (err != 0) + return err; + + err= clone_hdl->init(loc, loc_len, type, nullptr); + + /* Check and wait if clone is marked for wait. */ + if (err == 0) + { +#if 0 + auto timeout= get_ddl_timeout(thd); + /* zero timeout is special mode when DDL can abort running clone. */ + if (timeout == 0) + clone_hdl->set_ddl_abort(); +#endif + err= clone_sys->wait_for_free(thd); + } + + /* Re-check for initial errors as we could have released sys mutex + before allocating clone handle. */ + if (err == 0) + err = clone_begin_check(thd); + + if (err != 0) + { + clone_sys->drop_clone(clone_hdl); + return err; + } + } + + /* Add new task for the clone copy operation. */ + if (err == 0) + { + /* Release clone system mutex here as we might need to wait while + adding task. It is safe as the clone handle is acquired and cannot + be freed till we release it. */ + mysql_mutex_unlock(clone_sys->get_mutex()); + err= clone_hdl->add_task(thd, nullptr, 0, task_id); + + /* Open all tablespaces in Innodb if not done during bootstrap. */ + if (err == 0 && task_id == 0) + err= clone_init_tablespaces(thd); + mysql_mutex_lock(clone_sys->get_mutex()); + } + + if (err != 0) + { + clone_sys->drop_clone(clone_hdl); + return err; + } + + if (task_id > 0) + ib::info() << "Clone Begin Task ID: " << task_id; + + /* Get the current locator from clone handle. */ + loc= clone_hdl->get_locator(loc_len); + return 0; +} + +int innodb_clone_copy(THD *thd, const byte *loc, uint loc_len, uint task_id, + Ha_clone_stage stage, Ha_clone_cbk *cbk) +{ + /* Get clone handle by locator index. */ + auto clone_hdl= clone_sys->get_clone_by_index(loc, loc_len); + + auto err= clone_hdl->check_error(thd); + + ut_ad(stage >= HA_CLONE_STAGE_DDL_BLOCKED); + if (err != 0) + return err; + + /* Start data copy. */ + bool post_snapshot= (stage > HA_CLONE_STAGE_SNAPSHOT); + if (stage == HA_CLONE_STAGE_SNAPSHOT) + err= clone_hdl->snapshot(); + else + err= clone_hdl->copy(task_id, cbk, post_snapshot); + + clone_hdl->save_error(err); + return err; +} + +int innodb_clone_ack(THD *thd, const byte *loc, uint loc_len, + uint task_id, int in_err, Ha_clone_cbk *cbk) +{ + /* Check if reference locator is valid */ + if (loc != nullptr && !clone_validate_locator(loc, loc_len)) + { + int err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Locator"); + return err; + } + mysql_mutex_lock(clone_sys->get_mutex()); + + /* Find attach clone handle using the reference locator. */ + auto clone_hdl= clone_sys->find_clone(loc, loc_len, CLONE_HDL_COPY); + + mysql_mutex_unlock(clone_sys->get_mutex()); + + /* Must find existing clone for the locator */ + if (clone_hdl == nullptr) + { + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone ACK refers non-existing clone"); + return ER_INTERNAL_ERROR; + } + int err= 0; + + /* If thread is interrupted, then set interrupt error instead. */ + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + in_err= ER_QUERY_INTERRUPTED; + } + + if (in_err == 0) + { + /* Apply acknowledged data */ + err = clone_hdl->apply(thd, task_id, cbk); + clone_hdl->save_error(err); + } + else + { + /* For error input, return after saving it */ + ib::info() << "Clone set error ACK: " << in_err; + clone_hdl->save_error(in_err); + } + mysql_mutex_lock(clone_sys->get_mutex()); + + /* Detach from clone handle */ + clone_sys->drop_clone(clone_hdl); + + mysql_mutex_unlock(clone_sys->get_mutex()); + return err; +} + +/** Timeout while waiting for recipient after network failure. +@param[in,out] thd server thread handle +@return donor timeout in minutes. */ +static Clone_Min get_donor_timeout(THD *thd) +{ + int timeout = 0; + std::string config_timeout("clone_donor_timeout_after_network_failure"); + + if (!get_clone_timeout_config(thd, config_timeout, timeout)) + /* Default to five minutes in case error reading configuration. */ + timeout = 5; + + return Clone_Min(timeout); +} + +int innodb_clone_end(THD *thd, const byte *loc, uint loc_len, + uint task_id, int in_err) +{ + /* Acquire clone system mutex which would automatically get released + when we return from the function [RAII]. */ + Mysql_mutex_guard sys_mutex(clone_sys->get_mutex()); + + if (loc == nullptr) return 0; + /* Get clone handle by locator index. */ + Clone_Handle *clone_hdl= clone_sys->get_clone_by_index(loc, loc_len); + + /* If thread is interrupted, then set interrupt error instead. */ + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + in_err= ER_QUERY_INTERRUPTED; + } + /* Set error, if already not set */ + clone_hdl->save_error(in_err); + + /* Drop current task. */ + bool is_master= false; + auto wait_reconnect= clone_hdl->drop_task(thd, task_id, is_master); + auto is_copy= clone_hdl->is_copy_clone(); + auto is_init= clone_hdl->is_init(); + auto is_abort= clone_hdl->is_abort(); + + if (!wait_reconnect || is_abort) + { + if (is_copy && is_master) + { + if (is_abort) + { + ib::info() << "Clone Master aborted by concurrent clone"; + clone_hdl->set_abort(); + } + else if (in_err != 0) + /* Make sure re-start attempt fails immediately */ + clone_hdl->set_abort(); + } + + if (!is_copy && !is_init && is_master) + { + if (in_err == 0) + /* On success for apply handle, drop status file. */ + drop_status_file(clone_hdl); + else if (clone_hdl->replace_datadir()) + /* On failure, rollback if replacing current data directory. */ + clone_files_error(); + } + clone_sys->drop_clone(clone_hdl); + + auto da= thd->get_stmt_da(); + ib::info() + << "Clone" + << (is_copy ? " End" : (is_init ? " Apply Version End" : " Apply End")) + << (is_master ? " Master" : "") << " Task ID: " << task_id + << (in_err != 0 ? " Failed, code: " : " Passed, code: ") << in_err + << ": " + << ((in_err == 0 || da == nullptr || !da->is_error()) ? + "" : da->message()); + return 0; + } + + ut_ad(clone_hdl->is_copy_clone()); + ut_ad(is_master); + + auto da= thd->get_stmt_da(); + ib::info() + << "Clone Master n/w error code: " << in_err << ": " + << ((da == nullptr || !da->is_error()) ? "" : da->message()); + + auto time_out= get_donor_timeout(thd); + + if (time_out.count() <= 0) + { + ib::info() << "Clone Master Skip wait after n/w error. Dropping Snapshot."; + clone_sys->drop_clone(clone_hdl); + return 0; + } + + ib::info() << "Clone Master wait " << time_out.count() + << " minutes for restart after n/w error"; + + /* Set state to idle and wait for re-connect */ + clone_hdl->set_state(CLONE_STATE_IDLE); + /* Sleep for 1 second */ + Clone_Msec sleep_time(Clone_Sec(1)); + /* Generate alert message every minute. */ + Clone_Sec alert_interval(Clone_Min(1)); + + /* Wait for client to reconnect back */ + bool is_timeout= false; + auto err= Clone_Sys::wait(sleep_time, time_out, alert_interval, + [&](bool alert, bool &result) + { + mysql_mutex_assert_owner(clone_sys->get_mutex()); + result = !clone_hdl->is_active(); + + if (thd_killed(thd) || clone_hdl->is_interrupted()) + { + ib::info() + << "Clone End Master wait for Restart interrupted"; + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + + } + else if (Clone_Sys::s_clone_sys_state == CLONE_SYS_ABORT) + { + ib::info() + << "Clone End Master wait for Restart aborted by DDL"; + my_error(ER_CLONE_DDL_IN_PROGRESS, MYF(0)); + return ER_CLONE_DDL_IN_PROGRESS; + + } + else if (clone_hdl->is_abort()) + { + result= false; + ib::info() << "Clone End Master wait for Restart" + " aborted by concurrent clone"; + return 0; + } + + if (!result) + ib::info() << "Clone Master restarted successfully by " + "other task after n/w failure"; + else if (alert) + ib::info() << "Clone Master still waiting for restart"; + + return 0; + }, clone_sys->get_mutex(), is_timeout); + + if (err == 0 && is_timeout && clone_hdl->is_idle()) + ib::info() << "Clone End Master wait for restart timed out after " + << time_out.count() << " minutes. Dropping Snapshot"; + + /* If Clone snapshot is not restarted, at this point mark it for + abort and end the snapshot to allow any waiting DDL to unpin the + handle and exit. */ + if (!clone_hdl->is_active()) + { + ut_ad(err != 0 || is_timeout); + clone_hdl->set_abort(); + } + /* Last task should drop the clone handle. */ + clone_sys->drop_clone(clone_hdl); + return 0; +} + +int innodb_clone_apply_begin(THD *thd, const byte *&loc, + uint &loc_len, uint &task_id, Ha_clone_mode mode, + const char *data_dir) +{ + /* Check if reference locator is valid */ + if (loc != nullptr && !clone_validate_locator(loc, loc_len)) + { + int err= ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Locator"); + return err; + } + /* Acquire clone system mutex which would automatically get released + when we return from the function [RAII]. */ + Mysql_mutex_guard sys_mutex(clone_sys->get_mutex()); + + /* Check if clone is already in progress for the reference locator. */ + auto clone_hdl= clone_sys->find_clone(loc, loc_len, CLONE_HDL_APPLY); + + switch (mode) + { + case HA_CLONE_MODE_RESTART: + { + ib::info() << "Clone Apply Begin Master Task: Restart"; + auto err= clone_hdl->restart_apply(thd, loc, loc_len); + + /* Reduce reference count */ + clone_sys->drop_clone(clone_hdl); + + /* Restart is done by master task */ + ut_ad(task_id == 0); + task_id= 0; + + return err; + } + case HA_CLONE_MODE_START: + + if (clone_hdl != nullptr) + { + clone_sys->drop_clone(clone_hdl); + ib::error() << "Clone Apply Begin Master found duplicate clone"; + clone_hdl= nullptr; + ut_d(ut_error); + } + /* Check if the locator is from current mysqld server. */ + clone_hdl= clone_sys->find_clone(loc, loc_len, CLONE_HDL_COPY); + + if (clone_hdl != nullptr) + { + clone_sys->drop_clone(clone_hdl); + clone_hdl= nullptr; + ib::info() << "Clone Apply Master Loop Back"; + ut_ad(data_dir != nullptr); + } + ib::info() << "Clone Apply Begin Master Task"; + break; + + case HA_CLONE_MODE_ADD_TASK: + /* Should find existing clone for the locator */ + if (clone_hdl == nullptr) + { + /* Operation has finished already */ + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Apply add task to non-existing clone"); + return ER_INTERNAL_ERROR; + } + break; + + case HA_CLONE_MODE_VERSION: + /* Cannot have input locator or existing clone */ + ib::info() << "Clone Apply Begin Master Version Check"; + ut_ad(loc == nullptr); + ut_ad(clone_hdl == nullptr); + break; + + case HA_CLONE_MODE_MAX: + default: + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Appply Begin Invalid Mode"); + ut_d(ut_error); + return ER_INTERNAL_ERROR; + } + + if (clone_hdl == nullptr) + { + ut_ad(thd != nullptr); + + ut_ad(mode == HA_CLONE_MODE_VERSION || mode == HA_CLONE_MODE_START); + + /* Create new clone handle for apply. Reference locator + is used for matching the version. */ + auto err= clone_sys->add_clone(loc, CLONE_HDL_APPLY, clone_hdl); + if (err != 0) + return (err); + + err= clone_hdl->init(loc, loc_len, HA_CLONE_HYBRID, data_dir); + + if (err != 0) + { + clone_sys->drop_clone(clone_hdl); + return (err); + } + } + + if (clone_hdl->is_active()) + { + /* Release clone system mutex here as we might need to wait while + adding task. It is safe as the clone handle is acquired and cannot + be freed till we release it. */ + mysql_mutex_unlock(clone_sys->get_mutex()); + + /* Create status file to indicate active clone directory. */ + if (mode == HA_CLONE_MODE_START) + create_status_file(clone_hdl); + + int err= 0; + /* Drop any user data after acquiring backup lock. Don't allow + concurrent threads as the BACKUP MDL lock would not allow any + other threads to execute DDL. */ + if (clone_hdl->replace_datadir() && mode == HA_CLONE_MODE_START) + { + /* Safeguard to throw error if innodb read only mode is on. Currently + not reachable as we would get error much earlier while dropping user + tables. */ + if (srv_read_only_mode) + { + err= ER_INTERNAL_ERROR; + my_error(err, MYF(0), + "Clone cannot replace data with innodb_read_only = ON"); + ut_d(ut_error); + } + else + { + track_redo_files(); + /* TODO: err= clone_drop_user_data(thd, false); + if (err != 0) + clone_files_error(); + */ + } + } + + /* Add new task for the clone apply operation. */ + if (err == 0) + { + ut_ad(loc != nullptr); + err= clone_hdl->add_task(thd, loc, loc_len, task_id); + } + mysql_mutex_lock(clone_sys->get_mutex()); + + if (err != 0) + { + clone_sys->drop_clone(clone_hdl); + return err; + } + } + else + { + ut_ad(mode == HA_CLONE_MODE_VERSION); + /* Set all clone status files empty. */ + if (clone_hdl->replace_datadir()) + clone_init_list_files(); + } + + if (task_id > 0) + ib::info() << "Clone Apply Begin Task ID: " << task_id; + + /* Get the current locator from clone handle. */ + if (mode != HA_CLONE_MODE_ADD_TASK) + loc = clone_hdl->get_locator(loc_len); + + return 0; +} + +int innodb_clone_apply(THD *thd, const byte *loc, + uint loc_len, uint task_id, int in_err, + Ha_clone_cbk *cbk) +{ + /* Get clone handle by locator index. */ + auto clone_hdl= clone_sys->get_clone_by_index(loc, loc_len); + ut_ad(in_err != 0 || cbk != nullptr); + + /* For error input, return after saving it */ + if (in_err != 0 || cbk == nullptr) + { + clone_hdl->save_error(in_err); + auto da= thd->get_stmt_da(); + ib::info() + << "Clone Apply set error code: " << in_err << ": " + << ((in_err == 0 || da == nullptr || !da->is_error()) ? + "" : da->message()); + return 0; + } + + auto err= clone_hdl->check_error(thd); + if (err != 0) + return err; + + /* Apply data received from callback. */ + err= clone_hdl->apply(thd, task_id, cbk); + clone_hdl->save_error(err); + + return err; +} + +int innodb_clone_apply_end(THD *thd, const byte *loc, + uint loc_len, uint task_id, int in_err) +{ + auto err= innodb_clone_end(thd, loc, loc_len, task_id, in_err); + return err; +} + +/* Logical bitmap for clone file state. */ + +/** Data file is found. */ +const int FILE_DATA = 1; +/** Saved data file is found */ +const int FILE_SAVED = 10; +/** Cloned data file is found */ +const int FILE_CLONED = 100; + +/** NONE state: file not present. */ +const int FILE_STATE_NONE = 0; +/** Normal state: only data file is present. */ +const int FILE_STATE_NORMAL = FILE_DATA; +/** Saved state: only saved data file is present. */ +const int FILE_STATE_SAVED = FILE_SAVED; +/** Cloned state: data file and cloned data file are present. */ +const int FILE_STATE_CLONED = FILE_DATA + FILE_CLONED; +/** Saved clone state: saved data file and cloned data file are present. */ +const int FILE_STATE_CLONE_SAVED = FILE_SAVED + FILE_CLONED; +/** Replaced state: saved data file and data file are present. */ +const int FILE_STATE_REPLACED = FILE_SAVED + FILE_DATA; + +/* Clone data File state transfer. + [FILE_STATE_NORMAL] --> [FILE_STATE_CLONED] + Remote data is cloned into another file named .clone. + + [FILE_STATE_CLONED] --> [FILE_STATE_CLONE_SAVED] + Before recovery the datafile is saved in a file named .save. + + [FILE_STATE_CLONE_SAVED] --> [FILE_STATE_REPLACED] + Before recovery the cloned file is moved to datafile. + + [FILE_STATE_REPLACED] --> [FILE_STATE_NORMAL] + After successful recovery the saved data file is removed. + + Every state transition involves a single file create, delete or rename and + we consider them atomic. In case of a failure the state rolls back exactly + in reverse order. +*/ + +/** Check if a file exists. +@param[in] path file path name +@return true if file exists. */ +static bool os_file_exists(const char *path) +{ + os_file_type_t type; + bool exists= false; + bool ret= os_file_status(path, &exists, &type); + + return ret && exists; +} + +/** Get current state of a clone file. +@param[in] data_file data file name +@return current file state. */ +static int get_file_state(const std::string &data_file) +{ + int state = 0; + /* Check if data file is there. */ + if (os_file_exists(data_file.c_str())) + state += FILE_DATA; + + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + + /* Check if saved old file is there. */ + if (os_file_exists(saved_file.c_str())) + state += FILE_SAVED; + + std::string cloned_file(data_file); + cloned_file.append(CLONE_INNODB_REPLACED_FILE_EXTN); + + /* Check if cloned file is there. */ + if (os_file_exists(cloned_file.c_str())) + state += FILE_CLONED; + + return state; +} + +/** Roll forward clone file state till final state. +@param[in] data_file data file name +@param[in] final_state data file state to forward to +@return previous file state before roll forward. */ +static int file_roll_forward(const std::string &data_file, int final_state) +{ + auto cur_state= get_file_state(data_file); + + switch (cur_state) + { + case FILE_STATE_CLONED: + { + if (final_state == FILE_STATE_CLONED) + break; + /* Save data file */ + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + rename_file(data_file, saved_file); + ib::info() + << "Clone File Roll Forward: Save data file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_CLONE_SAVED: + { + if (final_state == FILE_STATE_CLONE_SAVED) + break; + /* Replace data file with cloned file. */ + std::string cloned_file(data_file); + cloned_file.append(CLONE_INNODB_REPLACED_FILE_EXTN); + rename_file(cloned_file, data_file); + ib::info() + << "Clone File Roll Forward: Rename clone to data file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_REPLACED: + { + if (final_state == FILE_STATE_REPLACED) + break; + /* Remove saved data file */ + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + remove_file(saved_file); + ib::info() + << "Clone File Roll Forward: Remove saved data file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_NORMAL: + /* Nothing to do. */ + break; + + default: + ib::fatal() + << "Clone File Roll Forward: Invalid File State: " << cur_state; + } + return cur_state; +} + +/** Roll back clone file state to normal state. +@param[in] data_file data file name */ +static void file_rollback(const std::string &data_file) +{ + auto cur_state= get_file_state(data_file); + switch (cur_state) + { + case FILE_STATE_REPLACED: + { + /* Replace data file back to cloned file. */ + std::string cloned_file(data_file); + cloned_file.append(CLONE_INNODB_REPLACED_FILE_EXTN); + rename_file(data_file, cloned_file); + ib::info() + << "Clone File Roll Back: Rename data to cloned file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_CLONE_SAVED: + { + /* Replace data file with saved file. */ + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + rename_file(saved_file, data_file); + ib::info() + << "Clone File Roll Back: Rename saved to data file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_CLONED: + { + /* Remove cloned data file. */ + std::string cloned_file(data_file); + cloned_file.append(CLONE_INNODB_REPLACED_FILE_EXTN); + remove_file(cloned_file); + ib::info() + << "Clone File Roll Back: Remove cloned file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_NORMAL: + /* Nothing to do. */ + break; + + default: + ib::fatal() + << "Clone File Roll Back: Invalid File State: " << cur_state; + } +} + +/* Clone old data File state transfer. These files are present only in +recipient and we haven't drop the database objects (table/tablespace) +before clone. Currently used for user created undo tablespace. Dropping +undo tablespace could be expensive as we need to wait for purge to finish. + [FILE_STATE_NORMAL] --> [FILE_STATE_SAVED] + Before recovery the old datafile is saved in a file named .save. + + [FILE_STATE_SAVED] --> [FILE_STATE_NONE] + After successful recovery the saved data file is removed. + + These state transitions involve a single file delete or rename and + we consider them atomic. In case of a failure the state rolls back. + + [FILE_STATE_SAVED] --> [FILE_STATE_NORMAL] + On failure saved data file is moved back to original data file. +*/ + +/** Roll forward old data file state till final state. +@param[in] data_file data file name +@param[in] final_state data file state to forward to */ +static void old_file_roll_forward(const std::string &data_file, + int final_state) +{ + auto cur_state= get_file_state(data_file); + + switch (cur_state) + { + case FILE_STATE_CLONED: + case FILE_STATE_CLONE_SAVED: + case FILE_STATE_REPLACED: + /* If the file is also cloned, we can skip here as it would be handled + with other cloned files. */ + ib::info() + << "Clone Old File Roll Forward: Skipped cloned file " << data_file + << " state: " << cur_state; + break; + case FILE_STATE_NORMAL: + { + if (final_state == FILE_STATE_NORMAL) + { + ut_d(ut_error); + break; + } + /* Save data file */ + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + rename_file(data_file, saved_file); + ib::info() + << "Clone Old File Roll Forward: Saved data file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_SAVED: + { + if (final_state == FILE_STATE_SAVED) + break; + /* Remove saved data file */ + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + remove_file(saved_file); + ib::info() + << "Clone Old File Roll Forward: Remove saved file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_NONE: + /* Nothing to do. */ + break; + + default: + ib::fatal() + << "Clone Old File Roll Forward: Invalid File State: " << cur_state; + } +} + +/** Roll back old data file state to normal state. +@param[in] data_file data file name */ +static void old_file_rollback(const std::string &data_file) +{ + auto cur_state= get_file_state(data_file); + + switch (cur_state) + { + case FILE_STATE_CLONED: + case FILE_STATE_CLONE_SAVED: + case FILE_STATE_REPLACED: + /* If the file is also cloned, we can skip here as it would be handled + with other cloned files. */ + ib::info() + << "Clone Old File Roll Back: Skip cloned file " << data_file + << " state: " << cur_state; + break; + + case FILE_STATE_SAVED: + { + /* Replace data file with saved file. */ + std::string saved_file(data_file); + saved_file.append(CLONE_INNODB_SAVED_FILE_EXTN); + rename_file(saved_file, data_file); + ib::info() + << "Clone Old File Roll Back: Renamed saved data file " << data_file + << " state: " << cur_state; + } + [[fallthrough]]; + + case FILE_STATE_NORMAL: + case FILE_STATE_NONE: + /* Nothing to do. */ + break; + + default: + ib::fatal() + << "Clone Old File Roll Back: Invalid File State: " << cur_state; + } +} + +/** Fatal error callback function. Don't call other functions from here. Don't +use ut_a, ut_ad asserts or ib::fatal to avoid recursive invocation. */ +static void clone_files_fatal_error() +{ + /* Safeguard to avoid recursive call. */ + static bool started_error_handling= false; + if (started_error_handling) + return; + + started_error_handling= true; + + std::ifstream err_file(CLONE_INNODB_ERROR_FILE); + if (err_file.is_open()) + err_file.close(); + else + { + /* Create error file if not there. */ + std::ofstream new_file(CLONE_INNODB_ERROR_FILE); + /* On creation failure, return and abort. */ + if (!new_file.is_open()) + return; + new_file.close(); + } + /* In case of fatal error, from ib::fatal and ut_a asserts + we terminate the process here and send the exit status so that a + managed server can be restarted with older data files. */ + // std::_Exit(MYSQLD_RESTART_EXIT); +} + +/** Update recovery status file at end of clone recovery. +@param[in] finished true if finishing clone recovery +@param[in] is_error if recovery error +@param[in] is_replace true, if replacing current directory */ +static void clone_update_recovery_status(bool finished, bool is_error, + bool is_replace) +{ + /* true, when we are recovering a cloned database. */ + static bool recovery_in_progress= false; + /* true, when replacing current data directory. */ + static bool recovery_replace= false; + + std::function callback_function; + + /* Mark the beginning of clone recovery. */ + if (!finished) + { + recovery_in_progress= true; + if (is_replace) + { + recovery_replace= true; + callback_function= clone_files_fatal_error; + ut_set_assert_callback(callback_function); + } + return; + } + is_replace= recovery_replace; + recovery_replace= false; + + /* Update status only if clone recovery in progress. */ + if (!recovery_in_progress) + return; + + /* Mark end of clone recovery process. */ + recovery_in_progress= false; + ut_set_assert_callback(callback_function); + + std::string file_name; + + file_name.assign(CLONE_INNODB_RECOVERY_FILE); + if (!file_exists(file_name)) + return; + + std::ofstream status_file; + status_file.open(file_name, std::ofstream::app); + if (!status_file.is_open()) + return; + + /* Write zero for unsuccessful recovery. */ + uint64_t end_time = 0; + if (is_error) + { + status_file << end_time << std::endl; + status_file.close(); + return; + } + + /* Write recovery end time */ + end_time= microsecond_interval_timer(); + status_file << end_time << std::endl; + if (!status_file.good()) + { + status_file.close(); + return; + } + + mtr_t mtr; + mtr.start(); + const buf_block_t* sys_blk= trx_sysf_get(&mtr); + byte *binlog_pos= buf_block_get_frame(sys_blk) + + TRX_SYS + TRX_SYS_MYSQL_LOG_INFO; + /* Check logfile magic number. */ + if (mach_read_from_4(binlog_pos + TRX_SYS_MYSQL_LOG_MAGIC_N_FLD) != + TRX_SYS_MYSQL_LOG_MAGIC_N) + { + mtr.commit(); + status_file.close(); + return; + } + /* Write binary log file name. */ + status_file << binlog_pos + TRX_SYS_MYSQL_LOG_NAME << std::endl; + if (!status_file.good()) + { + mtr.commit(); + status_file.close(); + return; + } + uint64_t log_offset= mach_read_from_8(binlog_pos + TRX_SYS_MYSQL_LOG_OFFSET); + + /* Write log file offset. */ + status_file << log_offset << std::endl; + + mtr.commit(); + status_file.close(); +} + +/** Initialize recovery status for cloned recovery. +@param[in] replace we are replacing current directory. */ +static void clone_init_recovery_status(bool replace) +{ + std::string file_name; + file_name.assign(CLONE_INNODB_RECOVERY_FILE); + + std::ofstream status_file; + status_file.open(file_name, std::ofstream::out | std::ofstream::trunc); + if (!status_file.is_open()) + return; + /* Write recovery begin time */ + uint64_t begin_time= microsecond_interval_timer(); + status_file << begin_time << std::endl; + status_file.close(); + clone_update_recovery_status(false, false, replace); +} + +/** Type of function which is supposed to handle a single file during +Clone operations, accepting the file's name (string). +@see clone_files_for_each_file */ +typedef std::function Clone_file_handler; + +/** Processes each file name listed in the given status file, executing a given +function for each of them. +@param[in] status_file_name status file name +@param[in] process the given function, accepting file name string +@return true iff status file was successfully opened */ +static bool clone_files_for_each_file(const char *status_file_name, + const Clone_file_handler &process) +{ + std::ifstream files; + files.open(status_file_name); + if (!files.is_open()) + return false; + + std::string data_file; + /* Extract and process all files listed in file with name=status_file_name */ + while (std::getline(files, data_file)) + process(data_file); + + files.close(); + return true; +} + +/** Process all entries and remove status file. +@param[in] file_name status file name +@param[in] process callback to process entries */ +static void process_remove_file(const char *file_name, + const Clone_file_handler &process) +{ + if (clone_files_for_each_file(file_name, process)) + { + std::string file_str(file_name); + remove_file(file_str); + } +} + +void clone_files_error() +{ + /* Check if clone file directory exists. */ + if (!os_file_exists(CLONE_FILES_DIR)) + return; + + std::string err_file(CLONE_INNODB_ERROR_FILE); + + /* Create error status file if not there. */ + if (!file_exists(err_file)) + create_file(err_file); + + /* Process all old files to be moved. */ + Clone_file_handler cbk = old_file_rollback; + process_remove_file(CLONE_INNODB_OLD_FILES, cbk); + + /* Process all files to be replaced. */ + cbk= file_rollback; + process_remove_file(CLONE_INNODB_REPLACED_FILES, cbk); + + /* Process all new files to be deleted. */ + cbk= remove_file; + process_remove_file(CLONE_INNODB_NEW_FILES, cbk); + + /* Process all temp ddl files to be deleted. */ + process_remove_file(CLONE_INNODB_DDL_FILES, cbk); + + /* Remove error status file. */ + remove_file(err_file); + + /* Update recovery status file for recovery error. */ + clone_update_recovery_status(true, true, true); +} + +#ifdef UNIV_DEBUG +bool clone_check_recovery_crashpoint(bool is_cloned_db) +{ + if (!is_cloned_db) + return true; + + std::string crash_file(CLONE_INNODB_RECOVERY_CRASH_POINT); + + if (file_exists(crash_file)) + { + remove_file(crash_file); + return false; + } + return true; +} +#endif + +void clone_files_recovery(bool finished) +{ + /* Clone error file is present in case of error. */ + std::string file_name; + file_name.assign(CLONE_INNODB_ERROR_FILE); + + if (file_exists(file_name)) + { + ut_ad(!finished); + clone_files_error(); + return; + } + + /* if replace file is not present, remove old file. */ + if (!finished) + { + std::string replace_files(CLONE_INNODB_REPLACED_FILES); + std::string old_files(CLONE_INNODB_OLD_FILES); + if (!file_exists(replace_files) && file_exists(old_files)) + { + remove_file(old_files); + ut_d(ut_error); + } + } + + /* Open files to get all old files to be saved or removed. Must handle + the old files before cloned files. This is because during old file + processing we need to skip the common files based on cloned state. If + the cloned state is reset then these files would be considered as old + files and removed. */ + int end_state = finished ? FILE_STATE_NONE : FILE_STATE_SAVED; + + auto old_file_handler= [end_state](const std::string &fname) + { + old_file_roll_forward(fname, end_state); + }; + + if (clone_files_for_each_file(CLONE_INNODB_OLD_FILES, old_file_handler)) + { + /* Remove clone file after successful recovery. */ + if (finished) + { + std::string old_files(CLONE_INNODB_OLD_FILES); + remove_file(old_files); + } + } + + /* Open file to get all files to be replaced. */ + end_state= finished ? FILE_STATE_NORMAL : FILE_STATE_REPLACED; + + std::ifstream files; + files.open(CLONE_INNODB_REPLACED_FILES); + + if (files.is_open()) + { + int prev_state= FILE_STATE_NORMAL; + /* If file is empty, it is not replace. */ + bool replace= false; + + /* Extract and process all files to be replaced */ + while (std::getline(files, file_name)) + { + replace= true; + prev_state= file_roll_forward(file_name, end_state); + } + files.close(); + + if (finished) + /* Update recovery status file at the end of clone recovery. We don't + remove the replace file here. It would be removed only after updating + GTID state. */ + clone_update_recovery_status(true, false, replace); + else + { + /* If previous state was normal, clone recovery is already done. */ + if (!replace || prev_state != FILE_STATE_NORMAL) + /* Clone database recovery is started. */ + clone_init_recovery_status(replace); + } + } + file_name.assign(CLONE_INNODB_NEW_FILES); + auto exists= file_exists(file_name); + + if (exists && finished) + { + /* Remove clone file after successful recovery. */ + std::string new_files(CLONE_INNODB_NEW_FILES); + remove_file(new_files); + } +} + +dberr_t clone_init() +{ + /* Check if incomplete cloned data directory */ + if (os_file_exists(CLONE_INNODB_IN_PROGRESS_FILE)) + return DB_ABORT_INCOMPLETE_CLONE; + + ignore_db_dirs_append(CLONE_FILES_DIR_NAME); + /* Initialize clone files before starting recovery. */ + clone_files_recovery(false); + + if (clone_sys == nullptr) + { + ut_ad(Clone_Sys::s_clone_sys_state == CLONE_SYS_INACTIVE); + clone_sys= UT_NEW(Clone_Sys(), mem_key_clone); + } + Clone_Sys::s_clone_sys_state= CLONE_SYS_ACTIVE; + + return DB_SUCCESS; +} + +void clone_free() +{ + if (clone_sys != nullptr) + { + ut_ad(Clone_Sys::s_clone_sys_state == CLONE_SYS_ACTIVE); + UT_DELETE(clone_sys); + clone_sys = nullptr; + } + Clone_Sys::s_clone_sys_state= CLONE_SYS_INACTIVE; +} + +bool clone_check_provisioning() { return Clone_handler::is_provisioning(); } + +bool clone_check_active() +{ + mysql_mutex_lock(clone_sys->get_mutex()); + auto is_active= clone_sys->check_active_clone(false); + mysql_mutex_unlock(clone_sys->get_mutex()); + + return (is_active || Clone_handler::is_provisioning()); +} + +/** TODO: Fix schema, table and tablespace. Used for two different purposes. +1. After recovery from cloned database: +A. Create empty data file for non-Innodb tables that are not cloned. +B. Create any schema directory that is not present. + +2. Before cloning into current data directory: +A. Drop all user tables. +B. Drop all user schema +C. Drop all user tablespaces. + +TODO: class Fixup_data + fix_cloned_tables() + clone_execute_query() + clone_drop_binary_logs() + clone_drop_user_data() */ + +Clone_notify::Clone_notify(Clone_notify::Type type, space_id_t space, + bool no_wait) + : m_space_id(space), + m_type(type), + m_wait(Wait_at::NONE), + m_blocked_state(), + m_error() +{ + if (clone_sys == nullptr) return; + DEBUG_SYNC_C("clone_notify_ddl"); + + if (fsp_is_system_temporary(space)) + /* No need to block clone. */ + return; + + std::string ntfn_mesg; + Mysql_mutex_guard sys_mutex(clone_sys->get_mutex()); + + bool clone_active= false; + Clone_Handle *clone_donor= nullptr; + + std::tie(clone_active, clone_donor)= clone_sys->check_active_clone(); + + /* This is for special case when clone_ddl_timeout is set to zero. DDL + needs to abort any running clone in this case. */ + if (clone_active && clone_donor->abort_by_ddl()) + { + clone_sys->mark_abort(true); + m_wait= Wait_at::ABORT; + return; + } + + if (type == Type::SYSTEM_REDO_RESIZE || + type == Type::SPACE_UNDO_TRUNCATE) + { + if (clone_active) + { + get_mesg(true, ntfn_mesg); + ib::info() << "Clone DDL Notification: " << ntfn_mesg; + + m_error = ER_CLONE_IN_PROGRESS; + my_error(ER_CLONE_IN_PROGRESS, MYF(0)); + return; + } + clone_sys->mark_abort(false); + m_wait= Wait_at::ABORT; + return; + } + + if (!clone_active) + { + /* Let any new clone block at the beginning. */ + clone_sys->mark_wait(); + m_wait = Wait_at::ENTER; + return; + } + + bool abort_if_failed= false; + + get_mesg(true, ntfn_mesg); + ib::info() << "Clone DDL Notification: " << ntfn_mesg; + + DEBUG_SYNC_C("clone_notify_ddl_before_state_block"); + + /* Check if clone needs to block at state change. */ + if (clone_sys->begin_ddl_state(m_type, m_space_id, no_wait, true, + m_blocked_state, m_error)) + { + m_wait= Wait_at::STATE_CHANGE; + ut_ad(!failed()); + return; + } + + DEBUG_SYNC_C("clone_notify_ddl_after_state_block"); + + DBUG_EXECUTE_IF("clone_ddl_error_abort", abort_if_failed = true;); + + /* Abort clone on failure, if requested. This is required when caller cannot + rollback on failure. Currently enable & disable encryption needs this. In + this case we need to force clone to abort. */ + if (failed() && abort_if_failed) + { + /* Clear any error raised. */ + m_error= 0; + auto thd= current_thd; + if (thd != nullptr) + { + thd->clear_error(); + thd->get_stmt_da()->reset_diagnostics_area(); + } + clone_sys->mark_abort(true); + m_wait= Wait_at::ABORT; + return; + } + ut_ad(m_wait == Wait_at::NONE); +} + +Clone_notify::~Clone_notify() +{ + if (clone_sys == nullptr) return; + + Mysql_mutex_guard sys_mutex(clone_sys->get_mutex()); + + switch (m_wait) + { + case Wait_at::ENTER: + clone_sys->mark_free(); + break; + + case Wait_at::STATE_CHANGE: + clone_sys->end_ddl_state(m_type, m_space_id, m_blocked_state); + break; + + case Wait_at::ABORT: + clone_sys->mark_active(); + break; + + case Wait_at::NONE: + [[fallthrough]]; + + default: + return; + } + + if (clone_sys->check_active_clone(false)) + { + std::string ntfn_mesg; + get_mesg(false, ntfn_mesg); + ib::info() << "Clone DDL Notification: " << ntfn_mesg; + } +} + +void Clone_notify::get_mesg(bool begin, std::string &mesg) +{ + if (begin) + mesg.assign("BEGIN "); + else + mesg.assign("END "); + + switch (m_type) + { + case Type::SYSTEM_REDO_RESIZE: + mesg.append("[SYSTEM_REDO_RESIZE] "); + break; + case Type::SPACE_UNDO_TRUNCATE: + mesg.append("[SPACE_UNDO_TRUNCATE] "); + break; + default: + mesg.append("[UNKNOWN] "); + break; + } + + if (m_space_id == UINT32_MAX) + return; + + mesg.append("Space ID: "); + mesg.append(std::to_string(m_space_id)); + + auto fil_space = fil_space_get(m_space_id); + if (fil_space == nullptr) + return; + + auto node= UT_LIST_GET_FIRST(fil_space->chain); + mesg.append(" File: "); + mesg.append(node->name); +} + +static int clone_init_tablespaces(THD *thd) +{ + if (clone_sys->is_space_initialized()) + return 0; + + /* TODO: Invoke BLOCK DDL MDL Lock service call. */ + /* We need to acquire X backup lock here to prevent DDLs. Clone by default + skips DDL lock. The API can handle recursive calls and it is not an issue + if clone has already acquired backup lock. */ + // auto timeout= static_cast(get_ddl_timeout(thd)); + + // if (acquire_exclusive_backup_lock(thd, timeout, false)) + // { + /* Timeout on backup lock. */ + // my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0)); + // return ER_LOCK_WAIT_TIMEOUT; + // } + ib::info() << "Clone: Started loading tablespaces"; + + dict_load_spaces_no_ddl(); + + // release_backup_lock(thd); + clone_sys->set_space_initialized(); + + ib::info() << "Clone: Finished loading tablespaces"; + return 0; +} + +Clone_Sys::Wait_stage::Wait_stage(const char *new_info) +{ + m_saved_info= nullptr; + THD *thd= current_thd; + + if (thd != nullptr) + { + m_saved_info= thd->get_proc_info(); + thd->proc_info= new_info; + } +} + +Clone_Sys::Wait_stage::~Wait_stage() +{ + THD *thd= current_thd; + + if (thd != nullptr && m_saved_info != nullptr) + thd->proc_info= m_saved_info; +} diff --git a/storage/innobase/clone/clone0apply.cc b/storage/innobase/clone/clone0apply.cc new file mode 100644 index 0000000000000..fec6ac273ebda --- /dev/null +++ b/storage/innobase/clone/clone0apply.cc @@ -0,0 +1,1731 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file clone/clone0apply.cc + Innodb apply snapshot data + + *******************************************************/ + +#include +#include + +#include "buf0dump.h" +#include "clone0api.h" +#include "clone0clone.h" +#include "dict0dict.h" +#include "handler.h" + +int Clone_Snapshot::get_file_from_desc(const Clone_File_Meta *file_meta, + const char *data_dir, bool desc_create, + bool &desc_exists, + Clone_file_ctx *&file_ctx) { + int err = 0; + + mysql_mutex_lock(&m_snapshot_mutex); + + auto idx = file_meta->m_file_index; + + ut_ad(m_snapshot_handle_type == CLONE_HDL_APPLY); + + ut_ad(m_snapshot_state == CLONE_SNAPSHOT_FILE_COPY || + m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY || + m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY); + + desc_exists = false; + + /* File metadata is already there, possibly sent by another task. */ + file_ctx = get_file_ctx_by_index(idx); + + if (file_ctx != nullptr) { + desc_exists = true; + + } else if (desc_create) { + /* Create the descriptor. */ + err = create_desc(data_dir, file_meta, false, file_ctx); + } + + mysql_mutex_unlock(&m_snapshot_mutex); + + return (err); +} + +int Clone_Snapshot::rename_desc(const Clone_File_Meta *file_meta, + const char *data_dir, + Clone_file_ctx *&file_ctx) { + /* Create new file context with new name. */ + auto err = create_desc(data_dir, file_meta, true, file_ctx); + + if (err != 0) { + return err; /* purecov: inspected */ + } + + file_ctx->m_state.store(Clone_file_ctx::State::RENAMED); + + /* Overwrite with the renamed file context. */ + add_file_from_desc(file_ctx, false); + + return 0; +} + +int Clone_Snapshot::fix_ddl_extension(const char *data_dir, + Clone_file_ctx *file_ctx) { + ut_ad(file_ctx->m_extension == Clone_file_ctx::Extension::DDL); + + /* If data directory is being replaced. */ + bool replace_dir = (data_dir == nullptr); + + auto file_meta = file_ctx->get_file_meta(); + bool is_undo_file = srv_is_undo_tablespace(file_meta->m_space_id); + bool is_redo_file = file_meta->m_space_id == SRV_SPACE_ID_UPPER_BOUND; + + auto extn = Clone_file_ctx::Extension::NONE; + const std::string file_path(file_meta->m_file_name); + + /* Check if file is already present and extension is needed. */ + auto err = handle_existing_file(replace_dir, is_undo_file, is_redo_file, + file_meta->m_file_index, file_path, extn); + if (err == 0) { + file_ctx->m_extension = extn; + } + + return err; +} + +int Clone_Snapshot::update_sys_file_name(bool replace, + const Clone_File_Meta *file_meta, + std::string &file_name) { + /* Currently needed only while replacing data directory. */ + if (!replace) { + return (0); + } + auto space_id = file_meta->m_space_id; + + /* Update buffer pool dump file path for provisioning. */ + if (space_id == UINT32_MAX) { + ut_ad(0 == strcmp(file_name.c_str(), SRV_BUF_DUMP_FILENAME_DEFAULT)); + + char path[OS_FILE_MAX_PATH]; + buf_dump_generate_path(path, sizeof(path)); + + file_name.assign(path); + return (0); + } + + /* Change name to system configured file when replacing current directory. */ + if (!is_system_tablespace(space_id)) { + return (0); + } + + /* Find out the node index of the file within system tablespace. */ + auto loop_index = file_meta->m_file_index; + + if (loop_index >= num_data_files()) { + /* purecov: begin deadcode */ + int err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid File Index"); + ut_d(ut_error); + return err; + /* purecov: end */ + } + + decltype(loop_index) node_index = 0; + + while (loop_index > 0) { + --loop_index; + auto file_ctx = get_file_ctx_by_index(loop_index); + auto cur_desc = file_ctx->get_file_meta(); + /* Loop through all files of current tablespace. */ + if (cur_desc->m_space_id != space_id) { + break; + } + ++node_index; + } + + auto last_file_index = + static_cast(srv_sys_space.m_files.size() - 1); + + /* Check if the file is beyond maximum configured files. */ + if (node_index > last_file_index) { + std::ostringstream err_strm; + err_strm << "innodb_data_file_path: Recipient file count: " + << last_file_index + 1 << " is less than Donor file count."; + + std::string err_str(err_strm.str()); + + my_error(ER_CLONE_SYS_CONFIG, MYF(0), err_str.c_str()); + + return (ER_CLONE_SYS_CONFIG); + } + + auto &file = srv_sys_space.m_files[node_index]; + auto page_sz= fil_space_t::physical_size(srv_sys_space.flags()); + + auto size_bytes = static_cast(file.size()); + size_bytes *= page_sz; + + /* Check if the file size matches with configured files. */ + if (file_meta->m_file_size != size_bytes) { + /* For last file it could mismatch if auto extend is specified. */ + if (node_index != last_file_index || + !srv_sys_space.can_auto_extend_last_file()) { + /* purecov: begin tested */ + std::ostringstream err_strm; + + err_strm << "innodb_data_file_path: Recipient value for " << node_index + << "th file size: " << size_bytes + << " doesn't match Donor file size: " << file_meta->m_file_size; + + std::string err_str(err_strm.str()); + + my_error(ER_CLONE_SYS_CONFIG, MYF(0), err_str.c_str()); + + return (ER_CLONE_SYS_CONFIG); + /* purecov: end */ + } + } + + /* Change filename to currently configured name. */ + file_name.assign(file.filepath()); + return (0); +} + +int Clone_Snapshot::handle_existing_file(bool replace, bool undo_file, + bool redo_file, + uint32_t data_file_index, + const std::string &data_file, + Clone_file_ctx::Extension &extn) { + /* We create one single redo log file at index 0. All other file data is + appended to it. For all other indexes no duplicate action is needed. */ + if (redo_file && data_file_index > 0) { + return 0; + } + extn = Clone_file_ctx::Extension::NONE; + /* For undo tablespace, check for duplicate file name. Currently it + is possible to create multiple undo tablespaces of same name under + different directory. This should not be recommended and in future + we aim to disallow specifying file name for tablespaces and generate + it internally based on space ID. Till that time, Clone needs to identify + and disallow undo tablespaces of same name as Clone creates all undo + tablespaces under innodb_undo_directory configuration in recipient. */ + if (undo_file) { + for (auto undo_index : m_undo_file_indexes) { + auto undo_file_ctx = get_file_ctx_by_index(undo_index); + if (undo_file_ctx == nullptr || undo_file_ctx->deleted()) { + continue; + } + auto undo_meta = undo_file_ctx->get_file_meta(); + + if (0 == strcmp(undo_meta->m_file_name, data_file.c_str())) { + /* purecov: begin tested */ + std::ostringstream err_strm; + err_strm << "Found multiple undo files with same name: " << data_file; + std::string err_str(err_strm.str()); + my_error(ER_CLONE_SYS_CONFIG, MYF(0), err_str.c_str()); + return (ER_CLONE_SYS_CONFIG); + /* purecov: end */ + } + } + m_undo_file_indexes.push_back(data_file_index); + /* With concurrent DDL support there could be deleted undo file + indexes here. At the end of every stage, new undo files could be + added limited by TRX_SYS_MAX_UNDO_SPACES. */ + ut_ad(m_undo_file_indexes.size() <= + CLONE_MAX_TRANSFER_STAGES * TRX_SYS_MAX_UNDO_SPACES); + } + + os_file_type_t type= OS_FILE_TYPE_UNKNOWN; + bool exists= false; + + bool ret= os_file_status(data_file.c_str(), &exists, &type); + if (ret && !exists) + { + int err= 0; + if (replace) { + /* Add file to new file list to enable rollback. */ + err= clone_add_to_list_file(CLONE_INNODB_NEW_FILES, data_file.c_str()); + } + /* Nothing to do if file doesn't exist. */ + extn= Clone_file_ctx::Extension::NONE; + return err; + } + + if (!ret || type != OS_FILE_TYPE_FILE) { + /* purecov: begin inspected */ + /* Either the stat() call failed or the name is a + directory/block device, or permission error etc. */ + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_ERROR_ON_WRITE, MYF(0), data_file.c_str(), errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return ER_ERROR_ON_WRITE; + /* purecov: end */ + } + + /* For cloning to different data directory, we must ensure that the + file is not present. This would always fail for local clone. */ + if (!replace) { + my_error(ER_FILE_EXISTS_ERROR, MYF(0), data_file.c_str()); + return ER_FILE_EXISTS_ERROR; + } + std::string clone_file= data_file + CLONE_INNODB_REPLACED_FILE_EXTN; + + /* Check that file with clone extension is not present */ + type= OS_FILE_TYPE_UNKNOWN; + exists= false; + ret= os_file_status(clone_file.c_str(), &exists, &type); + + if (ret && exists) + { + my_error(ER_FILE_EXISTS_ERROR, MYF(0), clone_file.c_str()); + return ER_FILE_EXISTS_ERROR; + } + extn= Clone_file_ctx::Extension::REPLACE; + + /* Add file name to files to be replaced before recovery. */ + return clone_add_to_list_file(CLONE_INNODB_REPLACED_FILES, data_file.c_str()); +} + +int Clone_Snapshot::build_file_path(const char *data_dir, + const Clone_File_Meta *file_meta, + std::string &built_path) +{ + std::string source; + + bool redo_file= (m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY); + bool absolute_path= false; + + if (!redo_file) + { + source.assign(file_meta->m_file_name); + + bool replace= (data_dir == nullptr); + auto err= update_sys_file_name(replace, file_meta, source); + + if (err != 0) + return err; + absolute_path= is_absolute_path(source.c_str()); + } + + /* For absolute path, copy the name and return. */ + if (absolute_path) + { + auto is_hard_path= test_if_hard_path(source.c_str()); + + /* Check if the absolute path is not in right format */ + if (is_hard_path == 0) + { + my_error(ER_WRONG_VALUE, MYF(0), "file path", source.c_str()); + return ER_WRONG_VALUE; + } + + built_path.assign(source); + return 0; + } + bool undo_file= srv_is_undo_tablespace(file_meta->m_space_id); + + /* Append appropriate data directory path. */ + if (data_dir != nullptr) + built_path= std::string{data_dir}; + else if (redo_file) + /* Use configured path when cloning into current data directory. */ + built_path= std::string{srv_log_group_home_dir}; + else if (undo_file) + built_path= std::string{srv_undo_dir}; + else + built_path = std::string{}; + + /* Add path separator if required. */ + if (!built_path.empty() && built_path.back() != OS_PATH_SEPARATOR) + built_path+= OS_PATH_SEPARATOR_STR; + + /* Add file name. For redo file use standard name. */ + if (redo_file) + { + /* This is redo file. Use standard name. */ + built_path+= LOG_FILE_NAME; + return 0; + } + ut_ad(!source.empty()); + + /* Remove dot slash prefix from source, if there. */ + std::string dot_slash= "."; + dot_slash+= OS_PATH_SEPARATOR; + if (std::equal(dot_slash.begin(), dot_slash.end(), source.begin())) + source.erase(0, 2); + + built_path+= source; + return 0; +} + +int Clone_Snapshot::build_file_ctx(Clone_file_ctx::Extension extn, + const Clone_File_Meta *file_meta, + const std::string &file_path, + Clone_file_ctx *&file_ctx) { + size_t alloc_size = sizeof(Clone_file_ctx) + file_path.length() + 1; + + /* Allocate for file path string. */ + auto path = static_cast(mem_heap_alloc(m_snapshot_heap, alloc_size)); + + if (path == nullptr) { + /* purecov: begin inspected */ + my_error(ER_OUTOFMEMORY, MYF(0), alloc_size); + return (ER_OUTOFMEMORY); + /* purecov: end */ + } + + /* Copy file metadata */ + file_ctx = reinterpret_cast(path); + file_ctx->init(extn); + path += sizeof(Clone_file_ctx); + + strcpy(path, file_path.c_str()); + + auto ctx_file_meta = file_ctx->get_file_meta(); + *ctx_file_meta = *file_meta; + + ctx_file_meta->m_file_name = static_cast(path); + + ctx_file_meta->m_file_name_len = file_path.length() + 1; + + ctx_file_meta->m_file_name_alloc_len = ctx_file_meta->m_file_name_len; + + return 0; +} + +/** Add directory path to file +@param[in] dir directory +@param[in] file file name +@param[out] path file along with path. */ +static void add_directory_path(const char *dir, const char *file, + std::string &path) { + path.clear(); + /* Append directory */ + if (dir != nullptr) { + path.assign(dir); + if (path.back() != OS_PATH_SEPARATOR) { + path.append(OS_PATH_SEPARATOR_STR); /* purecov: inspected */ + } + } + /* Append file */ + if (file != nullptr) { + path.append(file); + } +} + +int Clone_Snapshot::create_desc(const char *data_dir, + const Clone_File_Meta *file_meta, bool is_ddl, + Clone_file_ctx *&file_ctx) { + /* Update file path from configuration. */ + std::string file_path; + + auto err = build_file_path(data_dir, file_meta, file_path); + + if (err != 0) { + return (err); + } + + auto extn = Clone_file_ctx::Extension::NONE; + + if (is_ddl) { + extn = Clone_file_ctx::Extension::DDL; + + std::string ddl_list_file; + add_directory_path(data_dir, CLONE_INNODB_DDL_FILES, ddl_list_file); + + err = clone_add_to_list_file(ddl_list_file.c_str(), file_path.c_str()); + + } else { + /* If data directory is being replaced. */ + bool replace_dir = (data_dir == nullptr); + bool is_undo_file = srv_is_undo_tablespace(file_meta->m_space_id); + bool is_redo_file = file_meta->m_space_id == SRV_SPACE_ID_UPPER_BOUND; + + /* Check if file is already present in recipient. */ + err = handle_existing_file(replace_dir, is_undo_file, is_redo_file, + file_meta->m_file_index, file_path, extn); + } + + if (err == 0) { + /* Build complete path for the new file to be added. */ + err = build_file_ctx(extn, file_meta, file_path, file_ctx); + } + return (err); +} + +bool Clone_Snapshot::add_file_from_desc(Clone_file_ctx *&file_ctx, + bool ddl_create) { + mysql_mutex_lock(&m_snapshot_mutex); + + ut_ad(m_snapshot_handle_type == CLONE_HDL_APPLY); + auto file_meta = file_ctx->get_file_meta(); + + if (m_snapshot_state == CLONE_SNAPSHOT_FILE_COPY || + m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY) { + if (ddl_create) { + ut_a(file_meta->m_file_index == num_data_files()); + /* Add data file at the end and extend length. */ + m_data_file_vector.push_back(file_ctx); + } else { + m_data_file_vector[file_meta->m_file_index] = file_ctx; + } + } else { + ut_ad(m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY); + m_redo_file_vector[file_meta->m_file_index] = file_ctx; + } + + mysql_mutex_unlock(&m_snapshot_mutex); + + /** Check if it the last file */ + if (file_meta->m_file_index == num_data_files() - 1) { + return true; + } + + return (false); +} + +int Clone_Handle::apply_task_metadata(Clone_Task *task, + Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + uint desc_len = 0; + auto serial_desc = callback->get_data_desc(&desc_len); + + Clone_Desc_Task_Meta task_desc; + auto success = task_desc.deserialize(serial_desc, desc_len); + + if (!success) { + int err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Task Descriptor"); + ut_d(ut_error); + return err; + } + task->m_task_meta = task_desc.m_task_meta; + return (0); +} + +int Clone_Handle::check_space(const Clone_Task *task) { + /* Do space check only during file copy. */ + auto current_state = m_clone_task_manager.get_state(); + if (!task->m_is_master || current_state != CLONE_SNAPSHOT_FILE_COPY) { + return (0); + } + uint64_t free_space; + std::string MySQL_datadir_abs_path= mysql_real_data_home; + auto data_dir = + (replace_datadir() ? MySQL_datadir_abs_path.c_str() : get_datadir()); + + auto db_err = os_get_free_space(data_dir, free_space); + /* We skip space check if the OS interface returns error. */ + if (db_err != DB_SUCCESS) { + ib::warn() + << "Clone could not validate available free space"; + return (0); + } + + auto snapshot = m_clone_task_manager.get_snapshot(); + auto bytes_disk = snapshot->get_disk_estimate(); + + std::ostringstream avail_space; + std::ostringstream clone_space; + + avail_space << ib::bytes_iec{free_space}; + clone_space << ib::bytes_iec{bytes_disk}; + + int err = 0; + if (bytes_disk > free_space) { + err = ER_CLONE_DISK_SPACE; + my_error(err, MYF(0), clone_space.str().c_str(), avail_space.str().c_str()); + } + + ib::info() + << "Clone estimated size: " << clone_space.str().c_str() + << " Available space: " << avail_space.str().c_str(); + return (err); +} + +int Clone_Handle::apply_state_metadata(Clone_Task *task, + Ha_clone_cbk *callback) { + int err = 0; + uint desc_len = 0; + auto serial_desc = callback->get_data_desc(&desc_len); + + Clone_Desc_State state_desc; + auto success = state_desc.deserialize(serial_desc, desc_len); + + if (!success) { + err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid State Descriptor"); + ut_d(ut_error); + return err; + } + if (m_clone_handle_type == CLONE_HDL_COPY) { + ut_ad(state_desc.m_is_ack); + m_clone_task_manager.ack_state(&state_desc); + return (0); + } + + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + + /* ACK descriptor is sent for keeping the connection alive. */ + if (state_desc.m_is_ack) { + return (0); + } + + /* Reset current chunk information */ + auto &task_meta = task->m_task_meta; + task_meta.m_chunk_num = 0; + task_meta.m_block_num = 0; + + /* Move to the new state */ + if (state_desc.m_is_start) { +#ifdef UNIV_DEBUG + /* Network failure before moving to new state */ + err = m_clone_task_manager.debug_restart(task, err, 5); + if (err != 0) { + return err; + } +#endif /* UNIV_DEBUG */ + + /** Notify state change via callback. */ + notify_state_change(task, callback, &state_desc); + + err = fix_all_renamed(task); + + if (err == 0) { + err = move_to_next_state(task, nullptr, &state_desc); + } + +#ifdef UNIV_DEBUG + /* Network failure after moving to new state */ + err = m_clone_task_manager.debug_restart(task, err, 0); +#endif /* UNIV_DEBUG */ + + /* Check if enough space available on disk */ + if (err == 0) { + err = check_space(task); + } + + return (err); + } + + /* It is the end of current state. Close active file. */ + err = close_file(task); + +#ifdef UNIV_DEBUG + /* Network failure before finishing state */ + err = m_clone_task_manager.debug_restart(task, err, 2); +#endif /* UNIV_DEBUG */ + + if (err != 0) { + return (err); + } + + ut_ad(state_desc.m_state == m_clone_task_manager.get_state()); + + /* Mark current state finished for the task */ + err = m_clone_task_manager.finish_state(task); + +#ifdef UNIV_DEBUG + /* Network failure before sending ACK */ + err = m_clone_task_manager.debug_restart(task, err, 3); +#endif /* UNIV_DEBUG */ + + /* Send acknowledgement back to remote server */ + if (err == 0 && task->m_is_master) { + if (state_desc.m_state == CLONE_SNAPSHOT_FILE_COPY) { + DEBUG_SYNC_C("clone_file_copy_end_before_ack"); + } + err = ack_state_metadata(task, callback, &state_desc); + + if (err != 0) { + ib::info() + << "Clone Apply Master ACK finshed state: " << state_desc.m_state; + } + } + +#ifdef UNIV_DEBUG + /* Network failure after sending ACK */ + err = m_clone_task_manager.debug_restart(task, err, 4); +#endif /* UNIV_DEBUG */ + + return (err); +} + +void Clone_Handle::notify_state_change(Clone_Task *task, Ha_clone_cbk *callback, + Clone_Desc_State *state_desc) { + if (!task->m_is_master) { + return; + } + callback->mark_state_change(state_desc->m_estimate); + callback->buffer_cbk(nullptr, 0); + callback->clear_flags(); +} + +int Clone_Handle::ack_state_metadata(Clone_Task *, Ha_clone_cbk *callback, + Clone_Desc_State *state_desc) { + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + + state_desc->m_is_ack = true; + + byte desc_buf[CLONE_DESC_MAX_BASE_LEN]; + + auto serial_desc = &desc_buf[0]; + uint desc_len = CLONE_DESC_MAX_BASE_LEN; + + state_desc->serialize(serial_desc, desc_len, nullptr); + + callback->set_data_desc(serial_desc, desc_len); + callback->clear_flags(); + + auto err = callback->buffer_cbk(nullptr, 0); + + return (err); +} + +int Clone_Handle::apply_file_delete(Clone_Task *task, Clone_file_ctx *file_ctx, + const Clone_File_Meta *new_meta) { + auto err = close_file(task); + if (err != 0) { + return err; /* purecov: inspected */ + } + + auto file_meta = file_ctx->get_file_meta(); + + if (task->m_current_file_index != file_meta->m_file_index) { + task->m_current_file_index = file_meta->m_file_index; + } + + auto snapshot = m_clone_task_manager.get_snapshot(); + + auto begin_chunk = file_meta->m_begin_chunk; + auto end_chunk = file_meta->m_end_chunk; + auto block_num = snapshot->get_blocks_per_chunk(); + auto data_size = snapshot->get_chunk_size(); + + /* For page copy, we reset one page of the current chunk passed. Chunks + in file_meta corresponds to chunk in file copy. */ + if (snapshot->get_state() == CLONE_SNAPSHOT_PAGE_COPY) { + begin_chunk = new_meta->m_begin_chunk; + end_chunk = begin_chunk; + block_num = 0; + data_size = UNIV_PAGE_SIZE; + } + + Clone_Task_Meta new_task_meta = task->m_task_meta; + + /* Consume all chunks of deleted file. */ + for (auto cur_chunk = begin_chunk; cur_chunk <= end_chunk; ++cur_chunk) { + /* Set current chunk details. */ + new_task_meta.m_chunk_num = cur_chunk; + new_task_meta.m_block_num = block_num; + + if (m_clone_task_manager.is_chunk_reserved(cur_chunk)) { + continue; + } + + m_clone_task_manager.set_chunk(task, &new_task_meta); + + /* Set data size for progress estimation. */ + task->m_data_size = data_size; + } + + if (!file_ctx->deleted()) { + file_ctx->m_state.store(Clone_file_ctx::State::DROPPED); + } + + std::string old_file; + file_ctx->get_file_name(old_file); + + std::string mesg("FILE : "); + mesg.append(old_file); + mesg.append(" Space ID: "); + mesg.append(std::to_string(file_meta->m_space_id)); + mesg.append(" Chunks : "); + mesg.append(std::to_string(begin_chunk)); + mesg.append(" - "); + mesg.append(std::to_string(end_chunk)); + + ib::info() << "Clone DDL Invalidate : " << mesg; + return 0; +} + +int Clone_Handle::apply_ddl(const Clone_File_Meta *new_meta, + Clone_file_ctx *file_ctx) { + auto snapshot = m_clone_task_manager.get_snapshot(); + ut_ad(snapshot->get_state() == CLONE_SNAPSHOT_FILE_COPY || + snapshot->get_state() == CLONE_SNAPSHOT_PAGE_COPY); + + std::string old_file; + file_ctx->get_file_name(old_file); + + std::string mesg("DELETE FILE : "); + + if (new_meta->is_deleted()) { + /* Check if we have already deleted the file context. This is possible + in case of a network error and restart where donor could send the delete + request again. */ + if (file_ctx->m_state.load() == Clone_file_ctx::State::DROPPED_HANDLED) { + mesg.append(" IGNORE : "); + + } else { + /* File needs to be deleted. */ + if (!os_file_delete(innodb_clone_file_key, old_file.c_str())) { + /* purecov: begin inspected */ + mesg.append("Innodb Clone Apply Failed to delete file: "); + mesg.append(old_file); + my_error(ER_INTERNAL_ERROR, MYF(0), mesg.c_str()); + return ER_INTERNAL_ERROR; + /* purecov: end */ + } + file_ctx->m_state.store(Clone_file_ctx::State::DROPPED_HANDLED); + } + mesg.append(old_file); + mesg.append(" Space ID: "); + mesg.append(std::to_string(new_meta->m_space_id)); + + ib::info() << "Clone DDL APPLY: " << mesg; + return 0; + } + + auto old_meta = file_ctx->get_file_meta(); + + /* Check if file needs to be renamed. */ + if (!new_meta->is_renamed()) { + std::string update_mesg; + /* Set new encryption and compression type. */ + /* TODO: Handle if encryption is enabled/disabled. */ + if (old_meta->can_compress() != new_meta->can_compress()) { + old_meta->m_is_compressed = new_meta->m_is_compressed; + if (new_meta->can_compress()) + update_mesg.assign("UNCOMPRESSED "); + else + update_mesg.assign("COMPRESSED "); + } + + auto err = set_compression(file_ctx); + + std::string mesg("SET FILE "); + mesg.append(update_mesg); + mesg.append(": "); + mesg.append(old_file); + mesg.append(" Space ID: "); + mesg.append(std::to_string(new_meta->m_space_id)); + + ib::info() << "Clone DDL APPLY: " << mesg; + return err; + } + + Clone_file_ctx *new_ctx = nullptr; + + /* Rename file context. */ + auto err = snapshot->rename_desc(new_meta, m_clone_dir, new_ctx); + + if (err != 0) { + return err; /* purecov: inspected */ + } + + std::string new_file; + new_ctx->get_file_name(new_file); + + /* Preserve the old file size which could have been extended while applying + page 0 changes and set it to new descriptor. */ + auto file_meta = new_ctx->get_file_meta(); + auto file_size = file_meta->m_file_size; + + if (file_size < old_meta->m_file_size) { + file_size = old_meta->m_file_size; + } + file_meta->m_file_size = file_size; + + /* Do the actual rename. At this point we rename the files with temp DDL + extension. After all rename and delete requests are received we rename + the files again removing the ddl extension. This is required as file rename + requests are not in the real order and there could be conflicts. */ + ut_ad(new_ctx->m_extension == Clone_file_ctx::Extension::DDL); + + std::string rename_mesg("RENAME FILE WITH EXTN: "); + + if (old_file.compare(new_file) == 0) { + rename_mesg.append(" IGNORE : "); + + } else { + bool success = + os_file_rename(OS_CLONE_DATA_FILE, old_file.c_str(), new_file.c_str()); + + if (!success) { + /* purecov: begin inspected */ + char errbuf[MYSYS_STRERROR_SIZE]; + err = ER_ERROR_ON_RENAME; + + my_error(ER_ERROR_ON_RENAME, MYF(0), old_file.c_str(), new_file.c_str(), + errno, my_strerror(errbuf, sizeof(errbuf), errno)); + /* purecov: end */ + } + } + + rename_mesg.append(old_file); + rename_mesg.append(" to "); + rename_mesg.append(new_file); + rename_mesg.append(" Space ID: "); + rename_mesg.append(std::to_string(new_meta->m_space_id)); + + ib::info() << "Clone DDL APPLY: " << rename_mesg; + + if (err == 0) { + err = set_compression(new_ctx); + } + return err; +} + +int Clone_Handle::fix_all_renamed(const Clone_Task *task) { + /* Do space check only during file copy and page copy. */ + auto current_state = m_clone_task_manager.get_state(); + + bool fix_needed = current_state == CLONE_SNAPSHOT_FILE_COPY || + current_state == CLONE_SNAPSHOT_PAGE_COPY; + + if (!task->m_is_master || !fix_needed) { + return 0; + } + + auto snapshot = m_clone_task_manager.get_snapshot(); + + ut_ad(snapshot->get_state() == CLONE_SNAPSHOT_FILE_COPY || + snapshot->get_state() == CLONE_SNAPSHOT_PAGE_COPY); + + auto fix_func = [&](Clone_file_ctx *file_ctx) { + /* Need to handle files with DDL extension. */ + if (file_ctx->deleted() || + file_ctx->m_extension != Clone_file_ctx::Extension::DDL) { + return 0; + } + /* Save old file name */ + std::string old_file; + file_ctx->get_file_name(old_file); + + auto err = snapshot->fix_ddl_extension(m_clone_dir, file_ctx); + if (err != 0) { + return err; /* purecov: inspected */ + } + /* Get new file name. */ + std::string new_file; + file_ctx->get_file_name(new_file); + + /* Rename file */ + bool success = + os_file_rename(OS_CLONE_DATA_FILE, old_file.c_str(), new_file.c_str()); + if (!success) { + /* purecov: begin inspected */ + char errbuf[MYSYS_STRERROR_SIZE]; + err = ER_ERROR_ON_RENAME; + + my_error(ER_ERROR_ON_RENAME, MYF(0), old_file.c_str(), new_file.c_str(), + errno, my_strerror(errbuf, sizeof(errbuf), errno)); + /* purecov: end */ + } + + std::string mesg("RENAMED FILE REMOVED EXTN : "); + mesg.append(old_file); + mesg.append(" to "); + mesg.append(new_file); + mesg.append(" Space ID: "); + auto file_meta = file_ctx->get_file_meta_read(); + mesg.append(std::to_string(file_meta->m_space_id)); + + ib::info() << "Clone DDL APPLY: " << mesg; + return err; + }; + + auto err = snapshot->iterate_data_files(fix_func); + + /* Delete ddl list file. */ + if (err == 0) { + std::string ddl_list_file; + add_directory_path(m_clone_dir, CLONE_INNODB_DDL_FILES, ddl_list_file); + + clone_remove_list_file(ddl_list_file.c_str()); + } + + return err; +} + +/* Check and set punch hole for compressed page table. */ +int Clone_Handle::set_compression(Clone_file_ctx *file_ctx) { + auto file_meta = file_ctx->get_file_meta(); + + if (!file_meta->can_compress() || file_ctx->deleted()) + return 0; + + /* Disable punch hole if donor compression is not effective. */ + auto comp_type= fil_space_t::get_compression_algo(file_meta->m_fsp_flags); + if (comp_type == PAGE_UNCOMPRESSED || + file_meta->m_fsblk_size * 2 > srv_page_size) + { + file_meta->m_punch_hole= false; + return 0; + } + + os_file_stat_t stat_info; + std::string file_name; + file_ctx->get_file_name(file_name); + + os_file_get_status(file_name.c_str(), &stat_info, false, false); + + /* Check and disable punch hole if recipient cannot support it. */ + file_meta->m_punch_hole= (stat_info.block_size * 2 <= srv_page_size); + + /* Old format for compressed and encrypted page is + dependent on file system block size. */ + if (file_meta->can_encrypt() && + file_meta->m_fsblk_size != stat_info.block_size) { + auto donor_str= std::to_string(file_meta->m_fsblk_size); + auto recipient_str= std::to_string(stat_info.block_size); + + /* TODO: Check and get rid of this restriction. */ + my_error(ER_CLONE_CONFIG, MYF(0), "FS Block Size", donor_str.c_str(), + recipient_str.c_str()); + return ER_CLONE_CONFIG; + } + + return 0; +} + +int Clone_Handle::file_create_init(const Clone_file_ctx *file_ctx, + ulint file_type, bool init) +{ + /* Create the file and path. */ + File_init_cbk init_cbk= [&](pfs_os_file_t file) + { + if (!init) + return DB_SUCCESS; + + std::string file_name; + file_ctx->get_file_name(file_name); + + const auto file_meta= file_ctx->get_file_meta_read(); + bool is_undo_file= srv_is_undo_tablespace(file_meta->m_space_id); + + page_no_t size_in_pages= + is_undo_file ? SRV_UNDO_TABLESPACE_SIZE_IN_PAGES : FIL_IBD_FILE_INITIAL_SIZE; + + dberr_t db_err= DB_SUCCESS; + std::string mesg("CREATE NEW FILE : "); + + ut_ad(!file_meta->m_transfer_encryption_key); + /* TODO: 1. Check if initial pages need to be written. + 2. Write page header encryption information. */ + if (!os_file_set_size(file_name.c_str(), file, + size_in_pages << srv_page_size_shift, + file_meta->m_punch_hole)) + db_err= DB_OUT_OF_FILE_SPACE; + + mesg.append(file_name); + mesg.append(" Space ID: "); + mesg.append(std::to_string(file_meta->m_space_id)); + + if (db_err != DB_SUCCESS) + mesg.append(" FAILED"); + + ib::info() << "Clone DDL APPLY: " << mesg; + return db_err; + }; + + auto err= open_file(nullptr, file_ctx, file_type, true, init_cbk); + return err; +} + +int Clone_Handle::apply_file_metadata(Clone_Task *task, + Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + + uint desc_len = 0; + auto serial_desc = callback->get_data_desc(&desc_len); + + Clone_Desc_File_MetaData file_desc; + auto success = file_desc.deserialize(serial_desc, desc_len); + + if (!success) { + int err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid File Descriptor"); + ut_d(ut_error); + return err; + } + const auto file_desc_meta = &file_desc.m_file_meta; + auto snapshot = m_clone_task_manager.get_snapshot(); + + /* At end of current state DDL file alterations are communicated. */ + bool ddl_desc = (file_desc.m_state == snapshot->get_next_state()); + + ut_ad(ddl_desc || snapshot->get_state() == file_desc.m_state); + + bool file_deleted = file_desc_meta->is_deleted(); + + bool desc_exists = false; + Clone_file_ctx *file_ctx = nullptr; + + /* Check file metadata entry based on the descriptor. */ + auto err = snapshot->get_file_from_desc(file_desc_meta, m_clone_dir, false, + desc_exists, file_ctx); + if (err != 0) { + return (err); + } + + if (desc_exists) { + if (ddl_desc) { + err = apply_ddl(file_desc_meta, file_ctx); + + } else if (file_deleted) { + /* File delete notification sent immediately for chunk adjustment. */ + err = apply_file_delete(task, file_ctx, file_desc_meta); + } + return err; + } + + mysql_mutex_lock(m_clone_task_manager.get_mutex()); + + /* Create file metadata entry based on the descriptor. */ + err = snapshot->get_file_from_desc(file_desc_meta, m_clone_dir, true, + desc_exists, file_ctx); + if (err != 0 || desc_exists) { + mysql_mutex_unlock(m_clone_task_manager.get_mutex()); + + /* Save error with file name. */ + if (err != 0) { + m_clone_task_manager.set_error(err, file_desc_meta->m_file_name); + } + return (err); + } + + auto file_meta = file_ctx->get_file_meta(); + file_meta->m_punch_hole = false; + + bool is_file_copy = snapshot->get_state() == CLONE_SNAPSHOT_FILE_COPY; + bool is_page_copy = snapshot->get_state() == CLONE_SNAPSHOT_PAGE_COPY; + + if (is_file_copy || is_page_copy) { + ut_ad(is_file_copy || ddl_desc); + + auto file_type = OS_CLONE_DATA_FILE; + + if (file_meta->m_space_id == UINT32_MAX) { + file_type = OS_CLONE_LOG_FILE; + } + + if (file_deleted) { + /* Mark the newly created descriptor deleted. */ + file_ctx->m_state.store(Clone_file_ctx::State::DROPPED_HANDLED); + + std::string file_name; + file_ctx->get_file_name(file_name); + + std::string mesg("ADD DELETED FILE : "); + mesg.append(file_name); + mesg.append(" Space ID: "); + mesg.append(std::to_string(file_meta->m_space_id)); + ib::info() << "Clone DDL APPLY: " << mesg; + + } else { + /* Create the file and write initial pages if created by DDL. */ + err = file_create_init(file_ctx, file_type, ddl_desc); + } + + /* If last file is received, set all file metadata transferred */ + if (snapshot->add_file_from_desc(file_ctx, ddl_desc)) { + m_clone_task_manager.set_file_meta_transferred(); + } + + mysql_mutex_unlock(m_clone_task_manager.get_mutex()); + + if (err == 0 && file_type == OS_CLONE_DATA_FILE) { + err = set_compression(file_ctx); + } + return err; + } + + ut_ad(snapshot->get_state() == CLONE_SNAPSHOT_REDO_COPY); + ut_ad(file_desc.m_state == CLONE_SNAPSHOT_REDO_COPY); + ut_ad(!ddl_desc); + + /* open and reserve the redo file size */ + File_init_cbk empty_cbk; + + err = open_file(nullptr, file_ctx, OS_CLONE_LOG_FILE, true, empty_cbk); + + snapshot->add_file_from_desc(file_ctx, false); + + mysql_mutex_unlock(m_clone_task_manager.get_mutex()); + return (err); +} + +bool Clone_Handle::read_compressed_len(unsigned char *buffer, uint32_t len, + bool crc32, uint32_t block_size, + uint32_t &compressed_len) +{ + bool compressed=false; + if (crc32) + { + ut_a(len >= FIL_PAGE_TYPE + 2); + compressed_len= buf_page_full_crc32_size(buffer, &compressed, nullptr); + return compressed; + } + uint32_t header_len= FIL_PAGE_DATA; + + switch (fil_page_get_type(buffer)) + { + case FIL_PAGE_PAGE_COMPRESSED_ENCRYPTED: + header_len+= FIL_PAGE_ENCRYPT_COMP_METADATA_LEN; + break; + case FIL_PAGE_PAGE_COMPRESSED: + header_len+= FIL_PAGE_COMP_METADATA_LEN; + break; + default: + compressed_len= static_cast(srv_page_size); + return false; + } + + compressed=true; + ut_a(len >= FIL_PAGE_DATA + FIL_PAGE_COMP_SIZE + 2); + compressed_len= mach_read_from_2(buffer + FIL_PAGE_DATA + FIL_PAGE_COMP_SIZE); + + compressed_len+= header_len; + + /* Align compressed length. TODO: Check if this is required. */ + compressed_len= ut_calc_align(compressed_len, block_size); + return true; +} + +int Clone_Handle::sparse_file_write(Clone_File_Meta *file_meta, + unsigned char *buffer, uint32_t len, + pfs_os_file_t file, uint64_t start_off) { + dberr_t err= DB_SUCCESS; + auto page_len= fil_space_t::physical_size(file_meta->m_fsp_flags); + + /* Loop through all pages in current data block */ + while (len >= page_len) { + bool full_crc32= fil_space_t::full_crc32(file_meta->m_fsp_flags); + uint32_t comp_len; + bool is_compressed= read_compressed_len( + buffer, len, full_crc32, + static_cast(file_meta->m_fsblk_size), comp_len); + + auto write_len= is_compressed ? comp_len : page_len; + + /* Punch hole if needed */ + bool first_page= (start_off == 0); + + /* In rare case during file copy the page could be a torn page + and the size may not be correct. In such case the page is going to + be replaced later during page copy.*/ + if (first_page || write_len > page_len) + write_len= page_len; + + /* Write Data Page */ + errno= 0; + err= os_file_write(IORequestWrite, "Clone data file", file, + reinterpret_cast(buffer), start_off, + (start_off == 0) ? page_len : write_len); + if (err != DB_SUCCESS) + { + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_ERROR_ON_WRITE, MYF(0), file_meta->m_file_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + return ER_ERROR_ON_WRITE; + } + + os_offset_t offset= start_off + write_len; + os_offset_t hole_size= page_len - write_len; + + if (file_meta->m_punch_hole && hole_size > 0) + { + err= os_file_punch_hole(file.m_file, offset, hole_size); + if (err != DB_SUCCESS) + { + /* Disable for whole file */ + file_meta->m_punch_hole = false; + ut_ad(err == DB_IO_NO_PUNCH_HOLE); + ib::info() + << "Innodb Clone Apply failed to punch hole: " + << file_meta->m_file_name; + } + } + start_off+= page_len; + buffer+= page_len; + len-= page_len; + } + + /* Must have consumed all data. */ + ut_ad(err != DB_SUCCESS || len == 0); + return 0; +} + +int Clone_Handle::modify_and_write(const Clone_Task *task, uint64_t offset, + unsigned char *buffer, uint32_t buf_len) { + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + + auto snapshot = m_clone_task_manager.get_snapshot(); + auto file_meta = snapshot->get_file_by_index(task->m_current_file_index); + + ut_ad(!file_meta->can_encrypt()); + + if (file_meta->m_punch_hole) { + auto err = sparse_file_write(file_meta, buffer, buf_len, + task->m_current_file_des, offset); + return err; + } + + /* No more compression/encryption is needed. For redo/undo log files and + uncompressed tables, directly write to file */ + errno = 0; + auto db_err = + os_file_write(IORequestWrite, "Clone data file", task->m_current_file_des, + reinterpret_cast(buffer), offset, buf_len); + if (db_err != DB_SUCCESS) { + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_ERROR_ON_WRITE, MYF(0), file_meta->m_file_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + return (ER_ERROR_ON_WRITE); + } + return 0; +} + +int Clone_Handle::receive_data(Clone_Task *task, uint64_t offset, + uint64_t file_size, uint32_t size, + Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + + auto snapshot = m_clone_task_manager.get_snapshot(); + + auto file_ctx = snapshot->get_file_ctx_by_index(task->m_current_file_index); + auto file_meta = file_ctx->get_file_meta(); + + /* For redo log, offset is adjusted for single redo file. */ + offset = snapshot->get_apply_file_offset(file_meta->m_file_index, offset); + + std::string file_name; + file_ctx->get_file_name(file_name); + + /* If the file is deleted, then fetch the data and ignore. */ + if (file_ctx->deleted()) { + unsigned char *data_buf = nullptr; + uint32_t data_len = 0; + callback->apply_buffer_cbk(data_buf, data_len); + + std::string mesg("IGNORE DATA for DELETED FILE: "); + mesg.append(file_name); + mesg.append(" Space ID: "); + mesg.append(std::to_string(file_meta->m_space_id)); + + ib::info() << "Clone DDL APPLY: " << mesg; + return 0; + } + + bool is_page_copy = (snapshot->get_state() == CLONE_SNAPSHOT_PAGE_COPY); + bool is_log_file = (snapshot->get_state() == CLONE_SNAPSHOT_REDO_COPY); + + /* During page and redo copy, we encrypt the key in header page. */ + bool key_page = (is_page_copy && offset == 0); + bool key_log = (is_log_file && file_meta->m_file_index == 0 && offset == 0); + + if (key_page) { + /* Check and update file size for space header page */ + if (file_meta->m_file_size < file_size) { + snapshot->update_file_size(task->m_current_file_index, file_size); + } + } + + auto file_type = OS_CLONE_DATA_FILE; + + if (is_log_file || is_page_copy || + file_meta->m_space_id == UINT32_MAX || + file_meta->m_punch_hole) { + file_type = OS_CLONE_LOG_FILE; + } + + /* Open destination file for first block. */ + if (task->m_current_file_des.m_file == OS_FILE_CLOSED) { + ut_ad(file_meta != nullptr); + + File_init_cbk empty_cbk; + auto err = open_file(task, file_ctx, file_type, true, empty_cbk); + + if (err != 0) { + /* Save error with file name. */ + /* purecov: begin inspected */ + m_clone_task_manager.set_error(err, file_name.c_str()); + return (err); + /* purecov: end */ + } + } + + ut_ad(task->m_current_file_index == file_meta->m_file_index); + + /* Copy data to current destination file using callback. */ + char errbuf[MYSYS_STRERROR_SIZE]; + + auto file_hdl = task->m_current_file_des.m_file; + auto success = os_file_seek(nullptr, file_hdl, offset); + + if (!success) { + /* purecov: begin inspected */ + my_error(ER_ERROR_ON_READ, MYF(0), file_name.c_str(), errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + /* Save error with file name. */ + m_clone_task_manager.set_error(ER_ERROR_ON_READ, file_name.c_str()); + return (ER_ERROR_ON_READ); + /* purecov: end */ + } + + if (task->m_file_cache) { + callback->set_os_buffer_cache(); + /* For data file recommend zero copy for cached IO. */ + if (!is_log_file) { + callback->set_zero_copy(); + } + } + + callback->set_dest_name(file_meta->m_file_name); + + bool modify_buffer = false; + + /* In case of page compression we need to punch hole. */ + if (file_meta->m_punch_hole) { + ut_ad(!is_log_file); + modify_buffer = true; + } + + /* We need to encrypt the tablespace key by master key. */ + if (file_meta->can_encrypt() && (key_page || key_log)) { + modify_buffer = true; + } + auto err = file_callback(callback, task, size, modify_buffer, offset +#ifdef UNIV_PFS_IO + , __FILE__, __LINE__ +#endif /* UNIV_PFS_IO */ + ); + + task->m_data_size += size; + + if (err != 0) { + /* Save error with file name. */ + /* purecov: begin inspected */ + m_clone_task_manager.set_error(err, file_name.c_str()); + /* purecov: end */ + } + return (err); +} + +int Clone_Handle::apply_data(Clone_Task *task, Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_APPLY); + + /* Extract the data descriptor. */ + uint desc_len = 0; + auto serial_desc = callback->get_data_desc(&desc_len); + + Clone_Desc_Data data_desc; + auto success = data_desc.deserialize(serial_desc, desc_len); + + if (!success) { + int err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Data Descriptor"); + ut_d(ut_error); + return err; + } + /* Identify the task for the current block of data. */ + int err = 0; + auto task_meta = &data_desc.m_task_meta; + + /* The data is from a different file. Close the current one. */ + if (task->m_current_file_index != data_desc.m_file_index) { + err = close_file(task); + if (err != 0) { + return (err); + } + task->m_current_file_index = data_desc.m_file_index; + } + + /* Receive data from callback and apply. */ + err = receive_data(task, data_desc.m_file_offset, data_desc.m_file_size, + data_desc.m_data_len, callback); + + /* Close file in case of error. */ + if (err != 0) { + close_file(task); + } else { + err = m_clone_task_manager.set_chunk(task, task_meta); + } + + return (err); +} + +int Clone_Handle::apply(THD *, uint task_id, Ha_clone_cbk *callback) { + int err = 0; + uint desc_len = 0; + + auto clone_desc = callback->get_data_desc(&desc_len); + ut_ad(clone_desc != nullptr); + + Clone_Desc_Header header; + auto success = header.deserialize(clone_desc, desc_len); + + if (!success) { + err = ER_CLONE_PROTOCOL; + my_error(err, MYF(0), "Wrong Clone RPC: Invalid Descriptor Header"); + ut_d(ut_error); + return err; + } + + /* Check the descriptor type in header and apply */ + auto task = m_clone_task_manager.get_task_by_index(task_id); + + switch (header.m_type) { + case CLONE_DESC_TASK_METADATA: + err = apply_task_metadata(task, callback); + break; + + case CLONE_DESC_STATE: + err = apply_state_metadata(task, callback); + break; + + case CLONE_DESC_FILE_METADATA: + err = apply_file_metadata(task, callback); + break; + + case CLONE_DESC_DATA: + err = apply_data(task, callback); + break; + + default: + ut_d(ut_error); + break; + } + + if (err != 0) { + close_file(task); + } + + return (err); +} + +int Clone_Handle::restart_apply(THD *, const byte *&loc, uint &loc_len) { + auto init_loc = m_restart_loc; + auto init_len = m_restart_loc_len; + auto alloc_len = m_restart_loc_len; + + /* Get latest locator */ + loc = get_locator(loc_len); + + m_clone_task_manager.reinit_apply_state(loc, loc_len, init_loc, init_len, + alloc_len); + + /* Return the original locator if no state information */ + if (init_loc == nullptr) { + return (0); + } + + loc = init_loc; + loc_len = init_len; + + /* Reset restart loc buffer if newly allocated */ + if (alloc_len > m_restart_loc_len) { + m_restart_loc = init_loc; + m_restart_loc_len = alloc_len; + } + + ut_ad(loc == m_restart_loc); + + auto master_task = m_clone_task_manager.get_task_by_index(0); + + auto err = close_file(master_task); + + return (err); +} + +uint64_t Clone_Snapshot::get_apply_file_offset(uint32_t index, uint64_t offset) +{ + /* Adjustment needed only for multiple redo files being copied. */ + if (m_snapshot_state != CLONE_SNAPSHOT_REDO_COPY || index == 0) { + return offset; + } + for (uint32_t i = 0; i < index; ++i) + { + auto file_ctx = m_redo_file_vector[i]; + auto file_meta = file_ctx->get_file_meta(); + offset += (file_meta->m_file_size - log_t::START_OFFSET); + } + return offset; +} + +void Clone_Snapshot::update_file_size(uint32_t file_index, uint64_t file_size) { + /* Update file size when file is extended during page copy */ + ut_ad(m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY); + + auto cur_file = get_file_by_index(file_index); + + while (file_size > cur_file->m_file_size) { + ++file_index; + + if (file_index >= num_data_files()) { + /* Update file size for the last file. */ + cur_file->m_file_size = file_size; + break; + } + + auto next_file = get_file_by_index(file_index); + + if (next_file->m_space_id != cur_file->m_space_id) { + /* Update file size for the last file. */ + cur_file->m_file_size = file_size; + break; + } + + /* Only system tablespace can have multiple nodes. */ + ut_ad(cur_file->m_space_id == 0); + + file_size -= cur_file->m_file_size; + cur_file = next_file; + } +} + +int Clone_Snapshot::init_apply_state(Clone_Desc_State *state_desc) { + Mysql_mutex_guard guard(&m_snapshot_mutex); + + set_state_info(state_desc); + int err = 0; + + switch (m_snapshot_state) { + case CLONE_SNAPSHOT_FILE_COPY: + ib::info() << "Clone Apply State FILE COPY: "; + break; + + case CLONE_SNAPSHOT_PAGE_COPY: + ib::info() << "Clone Apply State PAGE COPY: "; + break; + + case CLONE_SNAPSHOT_REDO_COPY: + ib::info() << "Clone Apply State REDO COPY: "; + break; + + case CLONE_SNAPSHOT_DONE: + /* Extend and flush data files. */ + ib::info() << "Clone Apply State FLUSH DATA: "; + err = extend_and_flush_files(false); + if (err != 0) { + ib::info() + << "Clone Apply FLUSH DATA failed code: " << err; + break; + } + /* Flush redo files. */ + ib::info() << "Clone Apply State FLUSH REDO: "; + err = extend_and_flush_files(true); + if (err != 0) { + ib::info() + << "Clone Apply FLUSH REDO failed code: " << err; + break; + } + ib::info() << "Clone Apply State DONE"; + break; + + case CLONE_SNAPSHOT_NONE: + case CLONE_SNAPSHOT_INIT: + default: + err = ER_INTERNAL_ERROR; + my_error(err, MYF(0), "Innodb Clone Snapshot Invalid state"); + ut_d(ut_error); + break; + } + return (err); +} + +int Clone_Snapshot::extend_and_flush_files(bool flush_redo) { + auto &file_vector = (flush_redo) ? m_redo_file_vector : m_data_file_vector; + + for (auto file_ctx : file_vector) { + if (file_ctx->deleted()) { + ut_ad(file_ctx->m_state.load() == Clone_file_ctx::State::DROPPED_HANDLED); + continue; + } + char errbuf[MYSYS_STRERROR_SIZE]; + bool success = true; + auto file_meta = file_ctx->get_file_meta(); + + std::string file_name; + file_ctx->get_file_name(file_name); + + auto file = os_file_create( + innodb_clone_file_key, file_name.c_str(), OS_FILE_OPEN, + flush_redo ? OS_CLONE_LOG_FILE : OS_CLONE_DATA_FILE, false, &success); + + if (!success) { + /* purecov: begin inspected */ + my_error(ER_CANT_OPEN_FILE, MYF(0), file_name.c_str(), errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + return (ER_CANT_OPEN_FILE); + /* purecov: end */ + } + + auto file_size = os_file_get_size(file); + + size_t aligned_size = 0; + /* If file size is not aligned to extent size, recovery handling has + some issues. This work around eliminates dependency with that. */ + if (file_meta->m_fsp_flags != ULINT32_UNDEFINED) { + auto page_size= fil_space_t::physical_size(file_meta->m_fsp_flags); + auto extent_size= page_size * FSP_EXTENT_SIZE; + /* Skip extending files smaller than one extent. */ + if (file_size > extent_size) { + aligned_size = + static_cast(ut_uint64_align_up(file_size, extent_size)); + } + } + + if (file_size < file_meta->m_file_size || flush_redo) { + auto new_size = std::max(file_meta->m_file_size, file_size); + if (flush_redo) { + new_size = ut_uint64_align_up(file_size, UNIV_PAGE_SIZE_DEF); + new_size += UNIV_PAGE_SIZE_DEF; + } + success = os_file_set_size(file_name.c_str(), file, new_size); + } else if (file_size < aligned_size) { + success = os_file_set_size(file_name.c_str(), file, aligned_size); + } else { + success = os_file_flush(file); + } + + os_file_close(file); + + if (!success) { + /* purecov: begin inspected */ + my_error(ER_ERROR_ON_WRITE, MYF(0), file_name.c_str(), errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + return (ER_ERROR_ON_WRITE); + /* purecov: end */ + } + } + return (0); +} diff --git a/storage/innobase/clone/clone0clone.cc b/storage/innobase/clone/clone0clone.cc new file mode 100644 index 0000000000000..63d9fe7be9dda --- /dev/null +++ b/storage/innobase/clone/clone0clone.cc @@ -0,0 +1,2395 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file clone/clone0clone.cc + Innodb Clone System + + *******************************************************/ + +#include "clone0clone.h" +#include +#ifdef UNIV_DEBUG +#include "debug_sync.h" +#endif /* UNIV_DEBUG */ + +/** Global Clone System */ +Clone_Sys *clone_sys = nullptr; + +/** Clone System state */ +Clone_Sys_State Clone_Sys::s_clone_sys_state = {CLONE_SYS_INACTIVE}; + +/** Number of active abort requests */ +uint Clone_Sys::s_clone_abort_count = 0; + +/** Number of active wait requests */ +uint Clone_Sys::s_clone_wait_count = 0; + +Clone_Sys::Clone_Sys() + : m_clone_arr(), + m_num_clones(), + m_num_apply_clones(), + m_snapshot_arr(), + m_num_snapshots(), + m_num_apply_snapshots(), + m_clone_id_generator() { + mysql_mutex_init(0, &m_clone_sys_mutex, nullptr); + m_space_initialized.store(false); +} + +Clone_Sys::~Clone_Sys() { + mysql_mutex_destroy(&m_clone_sys_mutex); + +#ifdef UNIV_DEBUG + /* Verify that no active clone is present */ + int idx; + for (idx = 0; idx < CLONE_ARR_SIZE; idx++) { + ut_ad(m_clone_arr[idx] == nullptr); + } + ut_ad(m_num_clones == 0); + ut_ad(m_num_apply_clones == 0); + + for (idx = 0; idx < SNAPSHOT_ARR_SIZE; idx++) { + ut_ad(m_snapshot_arr[idx] == nullptr); + } + ut_ad(m_num_snapshots == 0); + ut_ad(m_num_apply_snapshots == 0); + +#endif /* UNIV_DEBUG */ +} + +Clone_Handle *Clone_Sys::find_clone(const byte *ref_loc, uint loc_len, + Clone_Handle_Type hdl_type) { + int idx; + bool match_found; + + Clone_Desc_Locator loc_desc; + Clone_Desc_Locator ref_desc; + Clone_Handle *clone_hdl; + + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + if (ref_loc == nullptr) { + return (nullptr); + } + + ref_desc.deserialize(ref_loc, loc_len, nullptr); + + match_found = false; + clone_hdl = nullptr; + + for (idx = 0; idx < CLONE_ARR_SIZE; idx++) { + clone_hdl = m_clone_arr[idx]; + + if (clone_hdl == nullptr || clone_hdl->is_init()) { + continue; + } + + if (clone_hdl->match_hdl_type(hdl_type)) { + clone_hdl->build_descriptor(&loc_desc); + + if (loc_desc.match(&ref_desc)) { + match_found = true; + break; + } + } + } + + if (match_found) { + clone_hdl->attach(); + return (clone_hdl); + } + + return (nullptr); +} + +int Clone_Sys::find_free_index(Clone_Handle_Type hdl_type, uint &free_index) { + free_index = CLONE_ARR_SIZE; + + uint target_index = CLONE_ARR_SIZE; + Clone_Handle *target_clone = nullptr; + + for (uint idx = 0; idx < CLONE_ARR_SIZE; idx++) { + auto clone_hdl = m_clone_arr[idx]; + + if (clone_hdl == nullptr) { + free_index = idx; + break; + } + + /* If existing clone has some error, it is on its way to exit. */ + auto err = clone_hdl->check_error(nullptr); + if (hdl_type == CLONE_HDL_COPY && (clone_hdl->is_idle() || err != 0)) { + target_clone = clone_hdl; + target_index = idx; + } + } + + if (free_index == CLONE_ARR_SIZE || + (hdl_type == CLONE_HDL_COPY && m_num_clones == MAX_CLONES) || + (hdl_type == CLONE_HDL_APPLY && m_num_apply_clones == MAX_CLONES)) { + if (target_clone == nullptr) { + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(0), MAX_CLONES); + return (ER_CLONE_TOO_MANY_CONCURRENT_CLONES); + } + } else { + return (0); + } + + /* We can abort idle clone and use the index. */ + ut_ad(target_clone != nullptr); + mysql_mutex_assert_owner(&m_clone_sys_mutex); + ut_ad(hdl_type == CLONE_HDL_COPY); + + target_clone->set_state(CLONE_STATE_ABORT); + + free_index = target_index; + + /* Sleep for 100 milliseconds. */ + Clone_Msec sleep_time(100); + /* Generate alert message every second. */ + Clone_Sec alert_interval(1); + /* Wait for 5 seconds for idle client to abort. */ + Clone_Sec time_out(5); + + bool is_timeout = false; + auto err = Clone_Sys::wait( + sleep_time, time_out, alert_interval, + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(clone_sys->get_mutex()); + auto current_clone = m_clone_arr[target_index]; + result = (current_clone != nullptr); + + if (thd_killed(current_thd)) { + ib::info() + << "Clone Begin Master wait for abort interrupted"; + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return (ER_QUERY_INTERRUPTED); + + } else if (Clone_Sys::s_clone_sys_state == CLONE_SYS_ABORT) { + ib::info() + << "Clone Begin Master wait for abort interrupted by DDL"; + my_error(ER_CLONE_DDL_IN_PROGRESS, MYF(0)); + return (ER_CLONE_DDL_IN_PROGRESS); + + } else if (result) { + if (!current_clone->is_abort()) { + /* Another clone has taken over the free index. */ + ib::info() + << "Clone Begin Master wait for abort interrupted"; + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return ER_QUERY_INTERRUPTED; + } + } + + if (!result) { + ib::info() << "Clone Master aborted idle task"; + + } else if (alert) { + ib::info() + << "Clone Master waiting for idle task abort"; + } + return (0); + }, + clone_sys->get_mutex(), is_timeout); + + if (err == 0 && is_timeout) { + ib::info() << "Clone Master wait for abort timed out"; + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Copy failed to abort idle clone [timeout]"); + err = ER_INTERNAL_ERROR; + } + return (err); +} + +int Clone_Sys::add_clone(const byte *loc, Clone_Handle_Type hdl_type, + Clone_Handle *&clone_hdl) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + ut_ad(m_num_clones <= MAX_CLONES); + ut_ad(m_num_apply_clones <= MAX_CLONES); + + auto version = choose_desc_version(loc); + + /* Find a free index to allocate new clone. */ + uint free_idx; + auto err = find_free_index(hdl_type, free_idx); + if (err != 0) { + return (err); + } + + /* Create a new clone. */ + clone_hdl = UT_NEW(Clone_Handle(hdl_type, version, free_idx), mem_key_clone); + + if (clone_hdl == nullptr) { + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(Clone_Handle)); + return (ER_OUTOFMEMORY); + } + + m_clone_arr[free_idx] = clone_hdl; + + if (hdl_type == CLONE_HDL_COPY) { + ++m_num_clones; + } else { + ut_ad(hdl_type == CLONE_HDL_APPLY); + ++m_num_apply_clones; + } + + clone_hdl->attach(); + + return (0); +} + +void Clone_Sys::drop_clone(Clone_Handle *clone_handle) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + if (clone_handle->detach() > 0) { + return; + } + + auto index = clone_handle->get_index(); + + ut_ad(m_clone_arr[index] == clone_handle); + + m_clone_arr[index] = nullptr; + + if (clone_handle->is_copy_clone()) { + ut_ad(m_num_clones > 0); + --m_num_clones; + + } else { + ut_ad(m_num_apply_clones > 0); + --m_num_apply_clones; + } + + UT_DELETE(clone_handle); +} + +Clone_Handle *Clone_Sys::get_clone_by_index(const byte *loc, uint loc_len) { + Clone_Desc_Locator loc_desc; + Clone_Handle *clone_hdl; + + loc_desc.deserialize(loc, loc_len, nullptr); + +#ifdef UNIV_DEBUG + Clone_Desc_Header *header = &loc_desc.m_header; + ut_ad(header->m_type == CLONE_DESC_LOCATOR); +#endif + clone_hdl = m_clone_arr[loc_desc.m_clone_index]; + + ut_ad(clone_hdl != nullptr); + + return (clone_hdl); +} + +int Clone_Sys::attach_snapshot(Clone_Handle_Type hdl_type, + Ha_clone_type clone_type, uint64_t snapshot_id, + bool is_pfs_monitor, Clone_Snapshot *&snapshot) { + uint idx; + uint free_idx = SNAPSHOT_ARR_SIZE; + + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + /* Try to attach to an existing snapshot. */ + for (idx = 0; idx < SNAPSHOT_ARR_SIZE; idx++) { + snapshot = m_snapshot_arr[idx]; + + if (snapshot != nullptr) { + if (snapshot->attach(hdl_type, is_pfs_monitor)) { + return (0); + } + } else if (free_idx == SNAPSHOT_ARR_SIZE) { + free_idx = idx; + } + } + + if (free_idx == SNAPSHOT_ARR_SIZE || + (hdl_type == CLONE_HDL_COPY && m_num_snapshots == MAX_SNAPSHOTS) || + (hdl_type == CLONE_HDL_APPLY && m_num_apply_snapshots == MAX_SNAPSHOTS)) { + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(0), MAX_SNAPSHOTS); + return (ER_CLONE_TOO_MANY_CONCURRENT_CLONES); + } + + /* Create a new snapshot. */ + snapshot = UT_NEW(Clone_Snapshot(hdl_type, clone_type, free_idx, snapshot_id), + mem_key_clone); + + if (snapshot == nullptr) { + my_error(ER_OUTOFMEMORY, MYF(0), sizeof(Clone_Snapshot)); + return (ER_OUTOFMEMORY); + } + + m_snapshot_arr[free_idx] = snapshot; + + if (hdl_type == CLONE_HDL_COPY) { + ++m_num_snapshots; + } else { + ut_ad(hdl_type == CLONE_HDL_APPLY); + ++m_num_apply_snapshots; + } + + snapshot->attach(hdl_type, is_pfs_monitor); + + return (0); +} + +void Clone_Sys::detach_snapshot(Clone_Snapshot *snapshot, + Clone_Handle_Type hdl_type) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + snapshot->detach(); + + /* Drop the snapshot. */ + uint index; + + index = snapshot->get_index(); + ut_ad(m_snapshot_arr[index] == snapshot); + + UT_DELETE(snapshot); + + m_snapshot_arr[index] = nullptr; + + if (hdl_type == CLONE_HDL_COPY) { + ut_ad(m_num_snapshots > 0); + --m_num_snapshots; + + } else { + ut_ad(hdl_type == CLONE_HDL_APPLY); + ut_ad(m_num_apply_snapshots > 0); + --m_num_apply_snapshots; + } +} + +Clone_Sys::Acquire_clone::Acquire_clone() { + std::tie(std::ignore, m_clone) = clone_sys->check_active_clone(); + + if (m_clone != nullptr) { + m_clone->attach(); + } +} + +Clone_Sys::Acquire_clone::~Acquire_clone() { + if (m_clone != nullptr) { + clone_sys->drop_clone(m_clone); + } + m_clone = nullptr; +} + +Clone_Snapshot *Clone_Sys::Acquire_clone::get_snapshot() { + if (m_clone == nullptr) { + return nullptr; /* purecov: inspected */ + } + return m_clone->get_snapshot(); +} + +bool Clone_Sys::check_active_clone(bool print_alert) { + bool active_clone = false; + std::tie(active_clone, std::ignore) = check_active_clone(); + + if (active_clone && print_alert) { + /* purecov: begin inspected */ + ib::info() << "DDL waiting for CLONE to abort"; + /* purecov: end */ + } + return (active_clone); +} + +std::tuple Clone_Sys::check_active_clone() { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + bool active_clone = false; + Clone_Handle *active_handle = nullptr; + + /* Check for active clone operations. */ + for (int idx = 0; idx < CLONE_ARR_SIZE; idx++) { + auto clone_hdl = m_clone_arr[idx]; + + if (clone_hdl != nullptr && clone_hdl->is_copy_clone()) { + active_clone = true; + active_handle = clone_hdl; + break; + } + } + return std::make_tuple(active_clone, active_handle); +} + +bool Clone_Sys::mark_abort(bool force) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + /* Check for active clone operations. Ignore clone, before initializing + space. It is safe as clone would check for abort request afterwards. We + require this check to prevent self deadlock when clone needs to create + space objects while initializing.*/ + + auto active_clone = is_space_initialized() && check_active_clone(false); + + /* If active clone is running and force is not set then + return without setting abort state. */ + if (active_clone && !force) { + return (false); + } + + ++s_clone_abort_count; + + if (s_clone_sys_state != CLONE_SYS_ABORT) { + ut_ad(s_clone_abort_count == 1); + s_clone_sys_state = CLONE_SYS_ABORT; + + DEBUG_SYNC_C("clone_marked_abort"); + } + + if (active_clone) { + ut_ad(force); + + /* Sleep for 1 second */ + Clone_Msec sleep_time(Clone_Sec(1)); + /* Generate alert message every minute. */ + Clone_Sec alert_time(Clone_Min(1)); + /* Timeout in 15 minutes - safeguard against hang, should not happen */ + Clone_Sec time_out(Clone_Min(15)); + + bool is_timeout = false; + + wait( + sleep_time, time_out, alert_time, + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + result = check_active_clone(alert); + + return (0); + }, + &m_clone_sys_mutex, is_timeout); + + if (is_timeout) { + ib::warn() << "DDL wait for CLONE abort timed out" + ", Continuing DDL."; + ut_d(ut_error); + } + } + return (true); +} + +void Clone_Sys::mark_active() { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + ut_ad(s_clone_abort_count > 0); + --s_clone_abort_count; + + if (s_clone_abort_count == 0) { + s_clone_sys_state = CLONE_SYS_ACTIVE; + } +} + +void Clone_Sys::mark_wait() { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + /* Let any new clone operation wait till mark_free is called. */ + ++s_clone_wait_count; +} + +void Clone_Sys::mark_free() { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + ut_ad(s_clone_wait_count > 0); + --s_clone_wait_count; +} + +#ifdef UNIV_DEBUG +void Clone_Sys::debug_wait_clone_begin() { + mysql_mutex_unlock(&m_clone_sys_mutex); + DEBUG_SYNC_C("clone_begin_wait_ddl"); + mysql_mutex_lock(&m_clone_sys_mutex); +} +#endif /* UNIV_DEBUG */ + +int Clone_Sys::wait_for_free(THD *thd) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + if (s_clone_wait_count == 0) { + return (0); + } + + auto wait_condition = [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + result = (s_clone_wait_count > 0); + if (alert) { + /* purecov: begin inspected */ + ib::info() + << "CLONE BEGIN waiting for DDL in critical section"; + /* purecov: end */ + } + + ut_d(debug_wait_clone_begin()); + + if (thd_killed(thd)) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return (ER_QUERY_INTERRUPTED); + } + + if (s_clone_sys_state == CLONE_SYS_ABORT) { + /* purecov: begin inspected */ + my_error(ER_CLONE_DDL_IN_PROGRESS, MYF(0)); + return ER_CLONE_DDL_IN_PROGRESS; + /* purecov: end */ + } + return (0); + }; + + /* Sleep for 100 milliseconds */ + Clone_Msec sleep_time(100); + /* Generate alert message 5 second. */ + Clone_Sec alert_time(5); + /* Timeout in 5 minutes - safeguard against hang, should not happen */ + Clone_Sec time_out(Clone_Min(5)); + + bool is_timeout = false; + auto err = wait(sleep_time, time_out, alert_time, wait_condition, + &m_clone_sys_mutex, is_timeout); + + if (err != 0) { + return (err); + } + + if (is_timeout) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "Clone BEGIN timeout waiting for DDL in critical section"); + ut_d(ut_error); + return ER_INTERNAL_ERROR; + } + + return (0); +} + +bool Clone_Sys::begin_ddl_state(Clone_notify::Type type, space_id_t space, + bool no_wait, bool check_intr, + uint32_t &blocked_state, int &error) { + mysql_mutex_assert_owner(get_mutex()); + Acquire_clone clone_handle; + + auto snapshot = clone_handle.get_snapshot(); + ut_ad(snapshot != nullptr); + blocked_state = CLONE_SNAPSHOT_NONE; + + if (snapshot == nullptr) { + return false; /* purecov: inspected */ + } + + DBUG_EXECUTE_IF("clone_ddl_error_abort", { + error = ER_INTERNAL_ERROR; + my_error(error, MYF(0), "Simulated Clone DDL error"); + return false; + }); + + /* Safe to release mutex after pinning the clone handle. */ + mysql_mutex_unlock(get_mutex()); + bool blocked = + snapshot->begin_ddl_state(type, space, no_wait, check_intr, error); + mysql_mutex_lock(get_mutex()); + + blocked_state = blocked ? snapshot->get_state() : CLONE_SNAPSHOT_NONE; + + return blocked; +} + +void Clone_Sys::end_ddl_state(Clone_notify::Type type, space_id_t space, + uint32_t blocked_state) { + mysql_mutex_assert_owner(get_mutex()); + Acquire_clone clone_handle; + + auto snapshot = clone_handle.get_snapshot(); + + /* Clone might have exited with error. */ + if (snapshot == nullptr) { + return; /* purecov: inspected */ + } + + if (blocked_state != snapshot->get_state()) { + /* purecov: begin deadcode */ + ib::error(); + ut_d(ut_error); + /* purecov: end */ + } + + /* Safe to release mutex after pinning the clone handle. */ + mysql_mutex_unlock(get_mutex()); + snapshot->end_ddl_state(type, space); + mysql_mutex_lock(get_mutex()); +} + +uint64_t Clone_Sys::get_next_id() { + mysql_mutex_assert_owner(&m_clone_sys_mutex); + + return (++m_clone_id_generator); +} + +#ifdef UNIV_DEBUG +bool Clone_Task_Manager::debug_sync_check(uint32_t chunk_num, + Clone_Task *task) { + auto nchunks = m_clone_snapshot->get_num_chunks(); + + /* Stop somewhere in the middle of current stage */ + if (!task->m_is_master || task->m_ignore_sync || + (chunk_num != 0 && chunk_num < (nchunks / 2 + 1))) { + return false; + } + + /* Ignore sync request for all future requests. */ + task->m_ignore_sync = true; + return true; +} + +void Clone_Task_Manager::debug_wait_ddl_meta() { + auto state = m_clone_snapshot->get_state(); + + /* We send DDL metadata of previous state. */ + if (state == CLONE_SNAPSHOT_PAGE_COPY) { + DEBUG_SYNC_C("clone_before_file_ddl_meta"); + + } else if (state == CLONE_SNAPSHOT_REDO_COPY) { + DEBUG_SYNC_C("clone_before_page_ddl_meta"); + } +} + +Clone_Task *Clone_Task_Manager::find_master_task() { + Clone_Task *task = nullptr; + + for (uint32_t index = 0; index < m_num_tasks; ++index) { + task = &m_clone_tasks[index]; + if (task->m_is_master) { + break; + } + } + return task; +} + +void Clone_Handle::close_master_file() { + auto task = m_clone_task_manager.find_master_task(); + close_and_unpin_file(task); +} + +void Clone_Sys::close_donor_master_file() { + Mysql_mutex_guard sys_mutex(get_mutex()); + + Clone_Handle *clone_donor = nullptr; + std::tie(std::ignore, clone_donor) = clone_sys->check_active_clone(); + + clone_donor->close_master_file(); +} + +void Clone_Task_Manager::debug_wait(uint chunk_num, Clone_Task *task) { + auto state = m_clone_snapshot->get_state(); + + if (!debug_sync_check(chunk_num, task)) { + return; + } + + /* We are releasing the donor PIN early in debug mode to allow concurrent DDL + after blocking here. The test need to ensure that it is local clone so that + donor master task context can be found. This is in recipient path. */ + DBUG_EXECUTE_IF("local_release_clone_file_pin", { + clone_sys->close_donor_master_file(); + ib::info() << "Clone debug close donor master file"; + }); + + if (state == CLONE_SNAPSHOT_FILE_COPY) { + DEBUG_SYNC_C("clone_file_copy"); + + } else if (state == CLONE_SNAPSHOT_PAGE_COPY) { + DEBUG_SYNC_C("clone_page_copy"); + + } else if (state == CLONE_SNAPSHOT_REDO_COPY) { + DEBUG_SYNC_C("clone_redo_copy"); + } +} + +int Clone_Task_Manager::debug_restart(Clone_Task *task, int in_err, + int restart_count) { + auto err = in_err; + + if (err != 0 || restart_count < task->m_debug_counter || !task->m_is_master) { + return (err); + } + + /* Restart somewhere in the middle of all chunks */ + if (restart_count == 1) { + auto nchunks = m_clone_snapshot->get_num_chunks(); + auto cur_chunk = task->m_task_meta.m_chunk_num; + + if (cur_chunk != 0 && cur_chunk < (nchunks / 2 + 1)) { + return (err); + } + } + + DBUG_EXECUTE_IF("clone_restart_apply", err = ER_NET_READ_ERROR;); + + if (err != 0) { + my_error(err, MYF(0)); + } + + /* Allow restart from next point */ + task->m_debug_counter = restart_count + 1; + + return (err); +} +#endif /* UNIV_DEBUG */ + +void Clone_Task_Manager::init(Clone_Snapshot *snapshot) { + uint idx; + + m_clone_snapshot = snapshot; + + m_current_state = snapshot->get_state(); + + /* ACK state is the previous state of current state */ + if (m_current_state == CLONE_SNAPSHOT_INIT) { + m_ack_state = CLONE_SNAPSHOT_NONE; + } else { + /* If clone is attaching to active snapshot with + other concurrent clone */ + ut_ad(m_current_state == CLONE_SNAPSHOT_FILE_COPY); + m_ack_state = CLONE_SNAPSHOT_INIT; + } + + m_chunk_info.m_total_chunks = 0; + + m_chunk_info.m_min_unres_chunk = 1; + m_chunk_info.m_max_res_chunk = 0; + + /* Initialize all tasks in inactive state. */ + for (idx = 0; idx < CLONE_MAX_TASKS; idx++) { + Clone_Task *task; + + task = m_clone_tasks + idx; + task->m_task_state = CLONE_TASK_INACTIVE; + + task->m_serial_desc = nullptr; + task->m_alloc_len = 0; + + task->m_current_file_des.m_file = OS_FILE_CLOSED; + task->m_pinned_file = false; + task->m_current_file_index = 0; + task->m_file_cache = true; + + task->m_current_buffer = nullptr; + task->m_buffer_alloc_len = 0; + task->m_is_master = false; + task->m_has_thd = false; + task->m_data_size = 0; + ut_d(task->m_ignore_sync = false); + ut_d(task->m_debug_counter = 2); + } + + m_num_tasks = 0; + m_num_tasks_finished = 0; + m_num_tasks_transit = 0; + m_restart_count = 0; + + m_next_state = CLONE_SNAPSHOT_NONE; + m_send_state_meta = false; + m_transferred_file_meta = false; + m_saved_error = 0; + + /* Initialize error file name */ + m_err_file_name.assign("Clone File"); +} + +void Clone_Task_Manager::reserve_task(THD *thd, uint &task_id) { + mysql_mutex_assert_owner(&m_state_mutex); + + Clone_Task *task = nullptr; + + task_id = 0; + + /* Find inactive task in the array. */ + for (; task_id < CLONE_MAX_TASKS; task_id++) { + task = m_clone_tasks + task_id; + auto task_meta = &task->m_task_meta; + + if (task->m_task_state == CLONE_TASK_INACTIVE) { + task->m_task_state = CLONE_TASK_ACTIVE; + + task_meta->m_task_index = task_id; + task_meta->m_chunk_num = 0; + task_meta->m_block_num = 0; + + /* Set first task as master task */ + if (task_id == 0) { + ut_ad(thd != nullptr); + task->m_is_master = true; + } + + /* Whether the task has an associated user session */ + task->m_has_thd = (thd != nullptr); + + break; + } + + task = nullptr; + } + + ut_ad(task != nullptr); +} + +int Clone_Task_Manager::alloc_buffer(Clone_Task *task) { + if (task->m_alloc_len != 0) { + /* Task buffers are already allocated in case + clone operation is restarted. */ + + ut_ad(task->m_buffer_alloc_len != 0); + ut_ad(task->m_serial_desc != nullptr); + ut_ad(task->m_current_buffer != nullptr); + + return (0); + } + + /* Allocate task descriptor. */ + auto heap = m_clone_snapshot->lock_heap(); + + /* Maximum variable length of descriptor. */ + auto alloc_len = + static_cast(m_clone_snapshot->get_max_file_name_length()); + + /* Check with maximum path name length. */ + if (alloc_len < FN_REFLEN_SE) { + alloc_len = FN_REFLEN_SE; + } + + /* Maximum fixed length of descriptor */ + alloc_len += CLONE_DESC_MAX_BASE_LEN; + + /* Add some buffer. */ + alloc_len += CLONE_DESC_MAX_BASE_LEN; + + ut_ad(task->m_alloc_len == 0); + ut_ad(task->m_buffer_alloc_len == 0); + + task->m_alloc_len = alloc_len; + task->m_buffer_alloc_len = m_clone_snapshot->get_dyn_buffer_length(); + + alloc_len += task->m_buffer_alloc_len; + + alloc_len += CLONE_ALIGN_DIRECT_IO; + + ut_ad(task->m_serial_desc == nullptr); + + task->m_serial_desc = static_cast(mem_heap_zalloc(heap, alloc_len)); + + m_clone_snapshot->release_heap(heap); + + if (task->m_serial_desc == nullptr) { + my_error(ER_OUTOFMEMORY, MYF(0), alloc_len); + return (ER_OUTOFMEMORY); + } + + if (task->m_buffer_alloc_len > 0) { + task->m_current_buffer = static_cast(ut_align( + task->m_serial_desc + task->m_alloc_len, CLONE_ALIGN_DIRECT_IO)); + } + + return (0); +} + +int Clone_Task_Manager::handle_error_other_task(bool set_error) { + char errbuf[MYSYS_STRERROR_SIZE]; + + if (set_error && m_saved_error != 0) { + ib::info() + << "Clone error from other task code: " << m_saved_error; + } + + if (!set_error) { + return (m_saved_error); + } + + /* Handle shutdown and KILL */ + if (thd_killed(current_thd)) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return (ER_QUERY_INTERRUPTED); + } + + /* Check if DDL has marked for abort. Ignore for client apply. */ + if ((m_clone_snapshot == nullptr || m_clone_snapshot->is_copy()) && + Clone_Sys::s_clone_sys_state == CLONE_SYS_ABORT) { + my_error(ER_CLONE_DDL_IN_PROGRESS, MYF(0)); + return (ER_CLONE_DDL_IN_PROGRESS); + } + + switch (m_saved_error) { + case ER_CLONE_DDL_IN_PROGRESS: + case ER_QUERY_INTERRUPTED: + my_error(m_saved_error, MYF(0)); + break; + + /* Network errors */ + case ER_NET_PACKET_TOO_LARGE: + case ER_NET_PACKETS_OUT_OF_ORDER: + case ER_NET_UNCOMPRESS_ERROR: + case ER_NET_READ_ERROR: + case ER_NET_READ_INTERRUPTED: + case ER_NET_ERROR_ON_WRITE: + case ER_NET_WRITE_INTERRUPTED: + // case ER_NET_WAIT_ERROR: + my_error(m_saved_error, MYF(0)); + break; + + /* IO Errors */ + case ER_CANT_OPEN_FILE: + case ER_CANT_CREATE_FILE: + case ER_ERROR_ON_READ: + case ER_ERROR_ON_WRITE: + /* purecov: begin inspected */ + my_error(m_saved_error, MYF(0), m_err_file_name.c_str(), errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + break; + /* purecov: end */ + + case ER_FILE_EXISTS_ERROR: + my_error(m_saved_error, MYF(0), m_err_file_name.c_str()); + break; + + case ER_WRONG_VALUE: + my_error(m_saved_error, MYF(0), "file path", m_err_file_name.c_str()); + break; + + case ER_CLONE_DONOR: + /* Will get the error message from remote */ + break; + + case 0: + break; + + default: + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone error in concurrent task"); + } + + return (m_saved_error); +} + +bool Clone_Task_Manager::wait_before_add(const byte *ref_loc, uint loc_len) { + mysql_mutex_assert_owner(&m_state_mutex); + + /* 1. Don't wait if master task. */ + if (m_num_tasks == 0) { + return (false); + } + + /* 2. Wait for state transition to get over */ + if (in_transit_state()) { + return (true); + } + + /* 3. For copy state(donor), wait for the state to reach file copy. */ + ut_ad(m_current_state != CLONE_SNAPSHOT_NONE); + if (ref_loc == nullptr) { + return (m_current_state == CLONE_SNAPSHOT_INIT); + } + + Clone_Desc_Locator ref_desc; + ref_desc.deserialize(ref_loc, loc_len, nullptr); + + ut_ad(m_current_state <= ref_desc.m_state); + + /* 4. For apply state (recipient), wait for apply state to reach + the copy state in reference locator. */ + if (m_current_state != ref_desc.m_state) { + return (true); + } + + /* 4A. For file copy state, wait for all metadata to be transferred. */ + if (m_current_state == CLONE_SNAPSHOT_FILE_COPY && + !is_file_metadata_transferred()) { + return (true); + } + return (false); +} + +int Clone_Task_Manager::add_task(THD *thd, const byte *ref_loc, uint loc_len, + uint &task_id) { + mysql_mutex_lock(&m_state_mutex); + + /* Check for error from other tasks */ + bool raise_error = (thd != nullptr); + + auto err = handle_error_other_task(raise_error); + + if (err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (err); + } + + if (wait_before_add(ref_loc, loc_len)) { + bool is_timeout = false; + int alert_count = 0; + err = Clone_Sys::wait_default( + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_state_mutex); + result = wait_before_add(ref_loc, loc_len); + + /* Check for error from other tasks */ + err = handle_error_other_task(raise_error); + + if (err == 0 && result && alert) { + /* Print messages every 1 minute - default is 5 seconds. */ + if (++alert_count == 12) { + alert_count = 0; + ib::info() << "Clone Add task waiting " + "for state change"; + } + } + return (err); + }, + &m_state_mutex, is_timeout); + + if (err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (err); + + } else if (is_timeout) { + ut_d(ut_error); +#ifndef UNIV_DEBUG + mysql_mutex_unlock(&m_state_mutex); + + ib::info() << "Clone Add task timed out"; + + my_error(ER_INTERNAL_ERROR, MYF(0), + "Clone Add task failed: " + "Wait too long for state transition"); + return (ER_INTERNAL_ERROR); +#endif + } + } + + /* We wait for state transition before adding new task. */ + ut_ad(!in_transit_state()); + + if (m_num_tasks == CLONE_MAX_TASKS) { + err = ER_CLONE_TOO_MANY_CONCURRENT_CLONES; + my_error(err, MYF(0), CLONE_MAX_TASKS); + + mysql_mutex_unlock(&m_state_mutex); + return (err); + } + + reserve_task(thd, task_id); + ut_ad(task_id <= m_num_tasks); + + ++m_num_tasks; + + mysql_mutex_unlock(&m_state_mutex); + return (0); +} + +bool Clone_Task_Manager::drop_task(THD *thd, uint task_id, bool &is_master) { + mysql_mutex_lock(&m_state_mutex); + + if (in_transit_state()) { + ut_ad(m_num_tasks_transit > 0); + --m_num_tasks_transit; + } + + ut_ad(m_num_tasks > 0); + --m_num_tasks; + + auto task = get_task_by_index(task_id); + + add_incomplete_chunk(task); + + reset_chunk(task); + + ut_ad(task->m_task_state == CLONE_TASK_ACTIVE); + task->m_task_state = CLONE_TASK_INACTIVE; + + is_master = task->m_is_master; + + if (!is_master) { + mysql_mutex_unlock(&m_state_mutex); + return (false); + } + + /* Master needs to wait for other tasks to get dropped */ + if (m_num_tasks > 0) { + bool is_timeout = false; + int alert_count = 0; + auto err = Clone_Sys::wait_default( + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_state_mutex); + result = (m_num_tasks > 0); + + if (thd_killed(thd)) { + return (ER_QUERY_INTERRUPTED); + + } else if (Clone_Sys::s_clone_sys_state == CLONE_SYS_ABORT) { + return (ER_CLONE_DDL_IN_PROGRESS); + } + if (alert && result) { + /* Print messages every 1 minute - default is 5 seconds. */ + if (++alert_count == 12) { + alert_count = 0; + ib::info() << "Clone Master drop task waiting " + "for other tasks"; + } + } + return (0); + }, + &m_state_mutex, is_timeout); + + if (err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (false); + + } else if (is_timeout) { + ib::info() << "Clone Master drop task timed out"; + + mysql_mutex_unlock(&m_state_mutex); + ut_d(ut_error); + return false; + } + } + + mysql_mutex_unlock(&m_state_mutex); + + /* Restart after network error */ + auto current_err = handle_error_other_task(false); + if (is_network_error(current_err)) { + return (true); + } + return (false); +} + +uint32_t Clone_Task_Manager::get_next_chunk() { + auto &max_chunk = m_chunk_info.m_max_res_chunk; + auto &min_chunk = m_chunk_info.m_min_unres_chunk; + + ut_ad(max_chunk <= m_chunk_info.m_total_chunks); + + if (min_chunk > m_chunk_info.m_total_chunks) { + /* No more chunks left for current state. */ + return (0); + } + + /* Return the minimum unreserved chunk */ + auto ret_chunk = min_chunk; + + /* Mark the chunk reserved. The chunk must be unreserved. */ + ut_ad(!m_chunk_info.m_reserved_chunks[min_chunk]); + m_chunk_info.m_reserved_chunks[min_chunk] = true; + + /* Increase max reserved chunk if needed */ + if (max_chunk < min_chunk) { + max_chunk = min_chunk; + } + + ut_ad(max_chunk == m_chunk_info.m_reserved_chunks.get_max_set_bit()); + + /* Set the next unreserved chunk */ + while (m_chunk_info.m_reserved_chunks[min_chunk]) { + ++min_chunk; + + /* Exit if all chunks are over */ + if (min_chunk > max_chunk || min_chunk > m_chunk_info.m_total_chunks) { + ut_ad(min_chunk > m_chunk_info.m_total_chunks || + !m_chunk_info.m_reserved_chunks[min_chunk]); + + break; + } + } + + return (ret_chunk); +} + +uint32_t Clone_Task_Manager::get_next_incomplete_chunk(uint32_t &block_num) { + block_num = 0; + + auto &chunks = m_chunk_info.m_incomplete_chunks; + + if (chunks.empty()) { + return (0); + } + + auto it = chunks.begin(); + + auto chunk_num = it->first; + + block_num = it->second; + + chunks.erase(it); + + return (chunk_num); +} + +int Clone_Task_Manager::reserve_next_chunk(Clone_Task *task, + uint32_t &ret_chunk, + uint32_t &ret_block) { + mysql_mutex_lock(&m_state_mutex); + ret_chunk = 0; + + /* Check for error from other tasks */ + auto err = handle_error_other_task(task->m_has_thd); + if (err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (err); + } + + if (process_inclomplete_chunk()) { + /* Get next incomplete chunk. */ + ret_chunk = get_next_incomplete_chunk(ret_block); + ut_ad(ret_chunk != 0); + + } else { + /* Get next unreserved chunk. */ + ret_block = 0; + ret_chunk = get_next_chunk(); + } + + reset_chunk(task); + mysql_mutex_unlock(&m_state_mutex); + return (0); +} + +int Clone_Task_Manager::set_chunk(Clone_Task *task, Clone_Task_Meta *new_meta) { + auto cur_meta = &task->m_task_meta; + int err = 0; + + ut_ad(cur_meta->m_task_index == new_meta->m_task_index); + cur_meta->m_task_index = new_meta->m_task_index; + + /* Check if this is a new chunk */ + if (cur_meta->m_chunk_num != new_meta->m_chunk_num) { + mysql_mutex_lock(&m_state_mutex); + + /* Mark the current chunk reserved */ + m_chunk_info.m_reserved_chunks[new_meta->m_chunk_num] = true; + + /* Check and remove the chunk from incomplete chunk list. */ + auto &chunks = m_chunk_info.m_incomplete_chunks; + + auto key_value = chunks.find(new_meta->m_chunk_num); + + if (key_value != chunks.end()) { + ut_ad(key_value->second < new_meta->m_block_num); + chunks.erase(key_value); + } + + reset_chunk(task); + + /* Check for error from other tasks */ + err = handle_error_other_task(task->m_has_thd); + + mysql_mutex_unlock(&m_state_mutex); + + cur_meta->m_chunk_num = new_meta->m_chunk_num; + +#ifdef UNIV_DEBUG + /* Network failure in the middle of a state */ + err = debug_restart(task, err, 1); + + /* Wait in the middle of state */ + debug_wait(cur_meta->m_chunk_num, task); +#endif /* UNIV_DEBUG */ + } + + cur_meta->m_block_num = new_meta->m_block_num; + + return (err); +} + +void Clone_Task_Manager::add_incomplete_chunk(Clone_Task *task) { + /* Track incomplete chunks during apply */ + if (m_clone_snapshot->is_copy()) { + return; + } + + auto &task_meta = task->m_task_meta; + + /* The task doesn't have any incomplete chunks */ + if (task_meta.m_chunk_num == 0) { + return; + } + + auto &chunks = m_chunk_info.m_incomplete_chunks; + + chunks[task_meta.m_chunk_num] = task_meta.m_block_num; + + ib::info() + << "Clone Apply add incomplete Chunk = " << task_meta.m_chunk_num + << " Block = " << task_meta.m_block_num + << " Task = " << task_meta.m_task_index; +} + +/** Print completed chunk information +@param[in] chunk_info chunk information */ +static void print_chunk_info(Chunk_Info *chunk_info) { + for (auto &chunk : chunk_info->m_incomplete_chunks) { + ib::info() + << "Incomplete: Chunk = " << chunk.first << " Block = " << chunk.second; + } + + auto min = chunk_info->m_reserved_chunks.get_min_unset_bit(); + auto max = chunk_info->m_reserved_chunks.get_max_set_bit(); + + auto size = chunk_info->m_reserved_chunks.size_bits(); + + ib::info() + << "Number of Chunks: " << size << " Min = " << min << " Max = " << max; + + ut_ad(min != max); + + if (max > min) { + ib::info() + << "Reserved Chunk Information : " << min << " - " << max + << " Chunks: " << max - min + 1; + + for (uint32_t index = min; index <= max;) { + uint32_t ind = 0; + + const int STR_SIZE = 64; + char str[STR_SIZE + 1]; + + while (index <= max && ind < STR_SIZE) { + str[ind] = chunk_info->m_reserved_chunks[index] ? '1' : '0'; + ++index; + ++ind; + } + + ut_ad(ind <= STR_SIZE); + str[ind] = '\0'; + + ib::info() << str; + } + } +} + +void Clone_Task_Manager::reinit_apply_state(const byte *ref_loc, uint ref_len, + byte *&new_loc, uint &new_len, + uint &alloc_len) { + ut_ad(m_current_state != CLONE_SNAPSHOT_NONE); + ut_ad(!m_clone_snapshot->is_copy()); + + /* Only master task should be present */ + ut_ad(m_num_tasks == 1); + + /* Reset State transition information */ + reset_transition(); + + /* Reset Error information */ + reset_error(); + + /* Check if current state is finished and acknowledged */ + ut_ad(m_ack_state <= m_current_state); + + if (m_ack_state == m_current_state) { + ++m_num_tasks_finished; + } + + ++m_restart_count; + + switch (m_current_state) { + case CLONE_SNAPSHOT_INIT: + ib::info() << "Clone Apply Restarting State: INIT"; + break; + + case CLONE_SNAPSHOT_FILE_COPY: + ib::info() + << "Clone Apply Restarting State: FILE COPY"; + break; + + case CLONE_SNAPSHOT_PAGE_COPY: + ib::info() + << "Clone Apply Restarting State: PAGE COPY"; + break; + + case CLONE_SNAPSHOT_REDO_COPY: + ib::info() + << "Clone Apply Restarting State: REDO COPY"; + break; + + case CLONE_SNAPSHOT_DONE: + ib::info() << "Clone Apply Restarting State: DONE"; + break; + + case CLONE_SNAPSHOT_NONE: + default: + ut_d(ut_error); + } + + if (m_current_state == CLONE_SNAPSHOT_INIT || + m_current_state == CLONE_SNAPSHOT_DONE || + m_current_state == CLONE_SNAPSHOT_NONE) { + new_loc = nullptr; + new_len = 0; + return; + } + + /* Add incomplete chunks from master task */ + auto task = get_task_by_index(0); + + add_incomplete_chunk(task); + + /* Reset task information */ + mysql_mutex_lock(&m_state_mutex); + reset_chunk(task); + mysql_mutex_unlock(&m_state_mutex); + + /* Allocate for locator if required */ + Clone_Desc_Locator temp_locator; + + temp_locator.deserialize(ref_loc, ref_len, nullptr); + + /* Update current state information */ + temp_locator.m_state = m_current_state; + + /* Update sub-state information */ + temp_locator.m_metadata_transferred = m_transferred_file_meta; + + auto len = temp_locator.m_header.m_length; + len += static_cast(m_chunk_info.get_serialized_length(0)); + + if (len > alloc_len) { + /* Allocate for more for possible reuse */ + len = CLONE_DESC_MAX_BASE_LEN; + ut_ad(len >= temp_locator.m_header.m_length); + + len += static_cast(m_chunk_info.get_serialized_length( + static_cast(CLONE_MAX_TASKS))); + + auto heap = m_clone_snapshot->lock_heap(); + + new_loc = static_cast(mem_heap_zalloc(heap, len)); + alloc_len = len; + + m_clone_snapshot->release_heap(heap); + } + + new_len = alloc_len; + + temp_locator.serialize(new_loc, new_len, &m_chunk_info, nullptr); + + print_chunk_info(&m_chunk_info); +} + +void Clone_Task_Manager::reinit_copy_state(const byte *loc, uint loc_len) { + ut_ad(m_clone_snapshot->is_copy()); + ut_ad(m_num_tasks == 0); + + mysql_mutex_lock(&m_state_mutex); + + /* Reset State transition information */ + reset_transition(); + + /* Reset Error information */ + reset_error(); + + ++m_restart_count; + + switch (m_current_state) { + case CLONE_SNAPSHOT_INIT: + ib::info() << "Clone Restarting State: INIT"; + break; + + case CLONE_SNAPSHOT_FILE_COPY: + ib::info() << "Clone Restarting State: FILE COPY"; + break; + + case CLONE_SNAPSHOT_PAGE_COPY: + ib::info() << "Clone Restarting State: PAGE COPY"; + break; + + case CLONE_SNAPSHOT_REDO_COPY: + ib::info() << "Clone Restarting State: REDO COPY"; + break; + + case CLONE_SNAPSHOT_DONE: + ib::info() << "Clone Restarting State: DONE"; + break; + + case CLONE_SNAPSHOT_NONE: + default: + ut_d(ut_error); + } + + if (m_current_state == CLONE_SNAPSHOT_NONE) { + mysql_mutex_unlock(&m_state_mutex); + ut_d(ut_error); + return; + } + + /* Reset to beginning of current state */ + init_state(); + + /* Compare local and remote state */ + Clone_Desc_Locator temp_locator; + + temp_locator.deserialize(loc, loc_len, nullptr); + + /* If Local state is ahead, we must have finished the + previous state confirmed by ACK. It is enough to + start from current state. */ + if (temp_locator.m_state != m_current_state) { +#ifdef UNIV_DEBUG + /* Current state could be just one state ahead */ + if (temp_locator.m_state == CLONE_SNAPSHOT_INIT) { + ut_ad(m_current_state == CLONE_SNAPSHOT_FILE_COPY); + + } else if (temp_locator.m_state == CLONE_SNAPSHOT_FILE_COPY) { + ut_ad(m_current_state == CLONE_SNAPSHOT_PAGE_COPY); + + } else if (temp_locator.m_state == CLONE_SNAPSHOT_PAGE_COPY) { + ut_ad(m_current_state == CLONE_SNAPSHOT_REDO_COPY); + + } else if (temp_locator.m_state == CLONE_SNAPSHOT_REDO_COPY) { + ut_ad(m_current_state == CLONE_SNAPSHOT_DONE); + + } else { + ut_d(ut_error); + } +#endif /* UNIV_DEBUG */ + + /* Apply state is behind. Need to send state metadata */ + m_send_state_meta = true; + + mysql_mutex_unlock(&m_state_mutex); + return; + } + + m_send_state_meta = false; + m_transferred_file_meta = temp_locator.m_metadata_transferred; + + /* Set progress information for current state */ + temp_locator.deserialize(loc, loc_len, &m_chunk_info); + + m_chunk_info.init_chunk_nums(); + + mysql_mutex_unlock(&m_state_mutex); + + print_chunk_info(&m_chunk_info); +} + +void Clone_Task_Manager::reinit_state() { + mysql_mutex_lock(&m_state_mutex); + init_state(); + mysql_mutex_unlock(&m_state_mutex); +} + +void Clone_Task_Manager::init_state() { + mysql_mutex_assert_owner(&m_state_mutex); + + auto num_chunks = m_clone_snapshot->get_num_chunks(); + + auto heap = m_clone_snapshot->lock_heap(); + + m_chunk_info.m_reserved_chunks.reset(num_chunks, heap); + + m_clone_snapshot->release_heap(heap); + + m_chunk_info.m_incomplete_chunks.clear(); + + m_chunk_info.m_min_unres_chunk = 1; + ut_ad(m_chunk_info.m_reserved_chunks.get_min_unset_bit() == 1); + + m_chunk_info.m_max_res_chunk = 0; + ut_ad(m_chunk_info.m_reserved_chunks.get_max_set_bit() == 0); + + m_chunk_info.m_total_chunks = num_chunks; +} + +void Clone_Task_Manager::ack_state(const Clone_Desc_State *state_desc) { + mysql_mutex_lock(&m_state_mutex); + + m_ack_state = state_desc->m_state; + ut_ad(m_current_state == m_ack_state); + ib::info() + << "Clone set state change ACK: " << m_ack_state; + + mysql_mutex_unlock(&m_state_mutex); +} + +int Clone_Task_Manager::wait_ack(Clone_Handle *clone, Clone_Task *task, + Ha_clone_cbk *callback) { + mysql_mutex_lock(&m_state_mutex); + + ++m_num_tasks_finished; + + /* All chunks are finished */ + reset_chunk(task); + + if (!task->m_is_master) { + mysql_mutex_unlock(&m_state_mutex); + return (0); + } + + int err = 0; + + if (m_current_state != m_ack_state) { + bool is_timeout = false; + int alert_count = 0; + err = Clone_Sys::wait_default( + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_state_mutex); + result = (m_current_state != m_ack_state); + + /* Check for error from other tasks */ + err = handle_error_other_task(task->m_has_thd); + + if (err == 0 && result && alert) { + /* Print messages every 1 minute - default is 5 seconds. */ + if (++alert_count == 12) { + alert_count = 0; + ib::info() << "Clone Master waiting " + "for state change ACK "; + } + err = clone->send_keep_alive(task, callback); + } + return (err); + }, + &m_state_mutex, is_timeout); + + /* Wait too long */ + if (err == 0 && is_timeout) { + ib::info() << "Clone Master wait for state change ACK" + " timed out"; + + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb clone state ack wait too long"); + + err = ER_INTERNAL_ERROR; + ut_d(ut_error); + } + } + mysql_mutex_unlock(&m_state_mutex); + + if (err == 0) { + ib::info() << "Clone Master received state change ACK"; + } + + return (err); +} + +int Clone_Task_Manager::finish_state(Clone_Task *task) { + mysql_mutex_lock(&m_state_mutex); + + if (task->m_is_master) { + /* Check if ACK was sent before restart */ + if (m_ack_state != m_current_state) { + ut_ad(m_ack_state < m_current_state); + ++m_num_tasks_finished; + } else { + ut_ad(m_restart_count > 0); + } + m_ack_state = m_current_state; + + } else { + ++m_num_tasks_finished; + } + + /* All chunks are finished */ + reset_chunk(task); + + /* Check for error from other tasks */ + auto err = handle_error_other_task(task->m_has_thd); + + if (!task->m_is_master || err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (err); + } + + ut_ad(task->m_is_master); + +#ifdef UNIV_DEBUG + /* Wait before ending state, if needed */ + if (!task->m_ignore_sync) { + mysql_mutex_unlock(&m_state_mutex); + debug_wait(0, task); + mysql_mutex_lock(&m_state_mutex); + } +#endif /* UNIV_DEBUG */ + + if (m_num_tasks_finished < m_num_tasks) { + bool is_timeout = false; + int alert_count = 0; + err = Clone_Sys::wait_default( + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(&m_state_mutex); + result = (m_num_tasks_finished < m_num_tasks); + + /* Check for error from other tasks */ + err = handle_error_other_task(task->m_has_thd); + + if (err == 0 && result && alert) { + /* Print messages every 1 minute - default is 5 seconds. */ + if (++alert_count == 12) { + alert_count = 0; + ib::info() + << "Clone Apply Master waiting for " + "workers before sending ACK." + << " Total = " << m_num_tasks + << " Finished = " << m_num_tasks_finished; + } + } + return (err); + }, + &m_state_mutex, is_timeout); + + if (err == 0 && is_timeout) { + ib::info() << "Clone Apply Master wait timed out"; + + my_error(ER_INTERNAL_ERROR, MYF(0), + "Clone Apply Master wait timed out before sending ACK"); + + err = ER_INTERNAL_ERROR; + ut_d(ut_error); + } + } + + mysql_mutex_unlock(&m_state_mutex); + return (err); +} + +int Clone_Task_Manager::change_state(Clone_Task *task, + Clone_Desc_State *state_desc, + Snapshot_State new_state, + Clone_Alert_Func cbk, uint &num_wait) { + mysql_mutex_lock(&m_state_mutex); + + num_wait = 0; + + /* Check for error from other tasks */ + auto err = handle_error_other_task(task->m_has_thd); + + if (err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (err); + } + + /* First requesting task needs to initiate the state transition. */ + if (!in_transit_state()) { + m_num_tasks_transit = m_num_tasks; + m_next_state = new_state; + } + + /* Master needs to wait for all other tasks. */ + if (task->m_is_master && m_num_tasks_transit > 1) { + num_wait = m_num_tasks_transit; + + mysql_mutex_unlock(&m_state_mutex); + return (0); + } + + /* Need to wait for transition to next state */ + if (!task->m_is_master) { + /* Move the current task over to the next state */ + ut_ad(m_num_tasks_transit > 0); + --m_num_tasks_transit; + + num_wait = m_num_tasks_transit; + ut_ad(num_wait > 0); + + mysql_mutex_unlock(&m_state_mutex); + return (0); + } + + /* Last task requesting the state change. All other tasks have + already moved over to next state and waiting for the transition + to complete. Now it is safe to do the snapshot state transition. */ + + ut_ad(task->m_is_master); + mysql_mutex_unlock(&m_state_mutex); + + if (m_clone_snapshot->is_copy()) { + ib::info() + << "Clone State Change : Number of tasks = " << m_num_tasks; + } else { + ib::info() + << "Clone Apply State Change : Number of tasks = " << m_num_tasks; + } + + err = m_clone_snapshot->change_state(state_desc, m_next_state, + task->m_current_buffer, + task->m_buffer_alloc_len, cbk); + + if (err != 0) { + return (err); + } + + mysql_mutex_lock(&m_state_mutex); + + /* Check for error from other tasks. Must finish the state transition + even in case of an error. */ + err = handle_error_other_task(task->m_has_thd); + + m_current_state = m_next_state; + m_next_state = CLONE_SNAPSHOT_NONE; + + --m_num_tasks_transit; + /* In case of error, the other tasks might have exited. */ + ut_ad(m_num_tasks_transit == 0 || err != 0); + m_num_tasks_transit = 0; + + /* For restart, m_num_tasks_finished may not be up to date */ + ut_ad(m_num_tasks_finished == m_num_tasks || err != 0); + m_num_tasks_finished = 0; + + ut_d(task->m_ignore_sync = false); + ut_d(task->m_debug_counter = 0); + + /* Initialize next state after transition. */ + init_state(); + + mysql_mutex_unlock(&m_state_mutex); + + return (err); +} + +int Clone_Task_Manager::check_state(Clone_Task *task, Snapshot_State new_state, + bool exit_on_wait, int in_err, + uint32_t &num_wait) { + mysql_mutex_lock(&m_state_mutex); + + num_wait = 0; + + if (in_err != 0) { + /* Save error for other tasks */ + if (m_saved_error == 0) { + m_saved_error = in_err; + } + /* Mark transit incomplete */ + if (in_transit_state()) { + ++m_num_tasks_transit; + } + mysql_mutex_unlock(&m_state_mutex); + return (in_err); + } + + /* Check for error from other tasks */ + auto err = handle_error_other_task(task->m_has_thd); + + if (err != 0) { + mysql_mutex_unlock(&m_state_mutex); + return (err); + } + + /* Check if current transition is still in progress. */ + if (in_transit_state() && new_state == m_next_state) { + num_wait = m_num_tasks_transit; + + ut_ad(num_wait > 0); + + if (exit_on_wait) { + /* Mark error for other tasks */ + m_saved_error = ER_INTERNAL_ERROR; + /* Mark transit incomplete */ + ++m_num_tasks_transit; + } + } + + mysql_mutex_unlock(&m_state_mutex); + + return (0); +} + +Clone_Handle::Clone_Handle(Clone_Handle_Type handle_type, uint clone_version, + uint clone_index) + : m_clone_handle_type(handle_type), + m_clone_handle_state(CLONE_STATE_INIT), + m_clone_locator(), + m_locator_length(), + m_restart_loc(), + m_restart_loc_len(), + m_clone_desc_version(clone_version), + m_clone_arr_index(clone_index), + m_clone_id(), + m_ref_count(), + m_allow_restart(false), + m_abort_ddl(false), + m_clone_dir(), + m_clone_task_manager() { + mysql_mutex_init(0, m_clone_task_manager.get_mutex(), nullptr); + + Clone_Desc_Locator loc_desc; + loc_desc.init(0, 0, CLONE_SNAPSHOT_NONE, clone_version, clone_index); + + auto loc = &m_version_locator[0]; + uint len = CLONE_DESC_MAX_BASE_LEN; + + memset(loc, 0, CLONE_DESC_MAX_BASE_LEN); + + loc_desc.serialize(loc, len, nullptr, nullptr); + + ut_ad(len <= CLONE_DESC_MAX_BASE_LEN); +} + +Clone_Handle::~Clone_Handle() { + mysql_mutex_destroy(m_clone_task_manager.get_mutex()); + + if (!is_init()) { + clone_sys->detach_snapshot(m_clone_task_manager.get_snapshot(), + m_clone_handle_type); + } + ut_ad(m_ref_count == 0); +} + +int Clone_Handle::create_clone_directory() { + ut_ad(!is_copy_clone()); + dberr_t db_err = DB_SUCCESS; + std::string file_name; + + if (!replace_datadir()) { + /* Create data directory, if we not replacing the current one. */ + db_err = os_file_create_subdirs_if_needed(m_clone_dir); + if (db_err == DB_SUCCESS) { + auto status = os_file_create_directory(m_clone_dir, false); + /* Create mysql schema directory. */ + file_name.assign(m_clone_dir); + file_name.append(OS_PATH_SEPARATOR_STR); + if (status) { + file_name.append("mysql"); + status = os_file_create_directory(file_name.c_str(), true); + } + if (!status) { + db_err = DB_ERROR; + } + } + file_name.assign(m_clone_dir); + file_name.append(OS_PATH_SEPARATOR_STR); + } + + /* Create clone status directory. */ + if (db_err == DB_SUCCESS) { + file_name.append(CLONE_FILES_DIR); + auto status = os_file_create_directory(file_name.c_str(), false); + if (!status) { + db_err = DB_ERROR; + } + } + /* Check and report error. */ + if (db_err != DB_SUCCESS) { + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_CANT_CREATE_DB, MYF(0), m_clone_dir, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + + return (ER_CANT_CREATE_DB); + } + return (0); +} + +int Clone_Handle::init(const byte *ref_loc, uint ref_len, Ha_clone_type type, + const char *data_dir) { + uint64_t snapshot_id; + Clone_Snapshot *snapshot; + + m_clone_dir = data_dir; + + bool enable_monitor = true; + + /* Generate unique clone identifiers for copy clone handle. */ + if (is_copy_clone()) { + m_clone_id = clone_sys->get_next_id(); + snapshot_id = clone_sys->get_next_id(); + + /* For local clone, monitor while applying data. */ + if (ref_loc == nullptr) { + enable_monitor = false; + } + + } else { + /* We don't provision instance on which active clone is running. */ + if (replace_datadir() && clone_sys->check_active_clone(false)) { + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(0), MAX_CLONES); + return (ER_CLONE_TOO_MANY_CONCURRENT_CLONES); + } + /* Return keeping the clone in INIT state. The locator + would only have the version information. */ + if (ref_loc == nullptr) { + return (0); + } + + auto err = create_clone_directory(); + if (err != 0) { + return (err); + } + + /* Set clone identifiers from reference locator for apply clone + handle. The reference locator is from copy clone handle. */ + Clone_Desc_Locator loc_desc; + + loc_desc.deserialize(ref_loc, ref_len, nullptr); + + m_clone_id = loc_desc.m_clone_id; + snapshot_id = loc_desc.m_snapshot_id; + + ut_ad(m_clone_id != CLONE_LOC_INVALID_ID); + ut_ad(snapshot_id != CLONE_LOC_INVALID_ID); + } + + /* Create and attach to snapshot. */ + auto err = clone_sys->attach_snapshot(m_clone_handle_type, type, snapshot_id, + enable_monitor, snapshot); + + if (err != 0) { + return (err); + } + + /* Initialize clone task manager. */ + m_clone_task_manager.init(snapshot); + + m_clone_handle_state = CLONE_STATE_ACTIVE; + + return (0); +} + +byte *Clone_Handle::get_locator(uint &loc_len) { + Clone_Desc_Locator loc_desc; + + /* Return version locator during initialization. */ + if (is_init()) { + loc_len = CLONE_DESC_MAX_BASE_LEN; + return (&m_version_locator[0]); + } + + auto snapshot = m_clone_task_manager.get_snapshot(); + + auto heap = snapshot->lock_heap(); + + build_descriptor(&loc_desc); + + loc_desc.serialize(m_clone_locator, m_locator_length, nullptr, heap); + + loc_len = m_locator_length; + + snapshot->release_heap(heap); + + return (m_clone_locator); +} + +void Clone_Handle::build_descriptor(Clone_Desc_Locator *loc_desc) { + Clone_Snapshot *snapshot; + uint64_t snapshot_id = CLONE_LOC_INVALID_ID; + Snapshot_State state = CLONE_SNAPSHOT_NONE; + + snapshot = m_clone_task_manager.get_snapshot(); + + if (snapshot) { + state = snapshot->get_state(); + snapshot_id = snapshot->get_id(); + } + + loc_desc->init(m_clone_id, snapshot_id, state, m_clone_desc_version, + m_clone_arr_index); +} + +bool Clone_Handle::drop_task(THD *thd, uint task_id, bool &is_master) { + /* No task is added in INIT state. The drop task is still called and + should be ignored. */ + if (is_init()) { + /* Only relevant for apply clone master */ + ut_ad(!is_copy_clone()); + ut_ad(task_id == 0); + is_master = true; + return (false); + } + /* Cannot be in IDLE state as master waits for tasks to drop before idling */ + ut_ad(!is_idle()); + + /* Close and reset file related information */ + auto task = m_clone_task_manager.get_task_by_index(task_id); + + close_file(task); + + mysql_mutex_assert_owner(clone_sys->get_mutex()); + mysql_mutex_unlock(clone_sys->get_mutex()); + + auto wait_restart = m_clone_task_manager.drop_task(thd, task_id, is_master); + mysql_mutex_lock(clone_sys->get_mutex()); + + /* Need to wait for restart, if network error */ + if (is_copy_clone() && m_allow_restart && wait_restart) { + ut_ad(is_master); + return (true); + } + + return (false); +} + +int Clone_Handle::move_to_next_state(Clone_Task *task, Ha_clone_cbk *callback, + Clone_Desc_State *state_desc) { + auto snapshot = m_clone_task_manager.get_snapshot(); + /* Use input state only for apply. */ + auto next_state = + is_copy_clone() ? snapshot->get_next_state() : state_desc->m_state; + + Clone_Alert_Func alert_callback; + + if (is_copy_clone()) { + /* Send Keep alive to recipient during long wait. */ + alert_callback = [&]() { + auto err = send_keep_alive(task, callback); + return (err); + }; + } + + /* Move to new state */ + uint num_wait = 0; + auto err = m_clone_task_manager.change_state(task, state_desc, next_state, + alert_callback, num_wait); + + /* Need to wait for all other tasks to move over, if any. */ + if (num_wait > 0) { + bool is_timeout = false; + int alert_count = 0; + err = Clone_Sys::wait_default( + [&](bool alert, bool &result) { + /* For multi threaded clone, master task does the state change. */ + if (task->m_is_master) { + err = m_clone_task_manager.change_state( + task, state_desc, next_state, alert_callback, num_wait); + } else { + err = m_clone_task_manager.check_state(task, next_state, false, 0, + num_wait); + } + result = (num_wait > 0); + + if (err == 0 && result && alert) { + /* Print messages every 1 minute - default is 5 seconds. */ + if (++alert_count == 12) { + alert_count = 0; + ib::info() << "Clone: master state change " + "waiting for workers"; + } + if (is_copy_clone()) { + err = send_keep_alive(task, callback); + } + } + return (err); + }, + nullptr, is_timeout); + + if (err == 0 && !is_timeout) { + return (0); + } + + if (!task->m_is_master) { + /* Exit from state transition */ + err = m_clone_task_manager.check_state(task, next_state, is_timeout, err, + num_wait); + if (err != 0 || num_wait == 0) { + return (err); + } + } + + if (err == 0 && is_timeout) { + ib::info() << "Clone: state change: " + "wait for other tasks timed out"; + + my_error(ER_INTERNAL_ERROR, MYF(0), + "Clone: state change wait for other tasks timed out: " + "Wait too long for state transition"); + ut_d(ut_error); + return ER_INTERNAL_ERROR; + } + } + return (err); +} + +void Clone_Handle::set_abort() { + set_state(CLONE_STATE_ABORT); + + Clone_Snapshot *snapshot = m_clone_task_manager.get_snapshot(); + + /* Clone is set to abort state and snapshot can never be reused. It is + safe to mark the snapshot aborted to let any waiting DDL exit. There + could be other tasks on their way to exit and we should not change + the snapshot state yet. */ + if (snapshot != nullptr) { + snapshot->set_abort(); + } +} + +int Clone_Handle::open_file(Clone_Task *task, const Clone_file_ctx *file_ctx, + ulint file_type, bool create_file, + File_init_cbk &init_cbk) +{ + std::string file_name; + file_ctx->get_file_name(file_name); + + /* Check if file exists */ + os_file_type_t type; + bool exists; + auto status= os_file_status(file_name.c_str(), &exists, &type); + + auto err_exit= [&](int in_err) + { + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(in_err, MYF(0), file_name.c_str(), errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return in_err; + }; + + if (!status) + return err_exit(ER_CANT_OPEN_FILE); + + os_file_create_t option; + bool read_only; + + if (create_file) + { + option= exists ? OS_FILE_OPEN : OS_FILE_CREATE; + read_only= false; + } else { + ut_ad(exists); + option= OS_FILE_OPEN; + read_only= true; + } + + if (option == OS_FILE_CREATE) + /* In case of a failure, we would use the error from os_file_create. */ + std::ignore= os_file_create_subdirs_if_needed(file_name.c_str()); + + bool success= false; + auto handle= os_file_create(innodb_clone_file_key, file_name.c_str(), option, + file_type, read_only, &success); + + int err= 0; + if (!success) + err= (option == OS_FILE_OPEN) ? ER_CANT_OPEN_FILE : ER_CANT_CREATE_FILE; + + else if (create_file && init_cbk) + { + auto db_err= init_cbk(handle); + + if (db_err != DB_SUCCESS) + { + os_file_close(handle); + err= ER_ERROR_ON_WRITE; + } + } + + if (err != 0) + return err_exit(err); + + if (task == nullptr) + { + ut_ad(create_file); + os_file_close(handle); + return 0; + } + + /* Set file descriptor in task. */ + close_file(task); + task->m_current_file_des = handle; + + ut_ad(handle.m_file != OS_FILE_CLOSED); + + task->m_file_cache= true; + + /* Set cache to false if direct IO(O_DIRECT) is used. */ + if (file_type == OS_CLONE_DATA_FILE) + { + task->m_file_cache= fil_system.is_buffered(); + DBUG_EXECUTE_IF("clone_no_zero_copy", task->m_file_cache= false;); + } + auto file_meta= file_ctx->get_file_meta_read(); + + /* If the task has pinned file, the index should be set. */ + ut_ad(!task->m_pinned_file || + task->m_current_file_index == file_meta->m_file_index); + + task->m_current_file_index= file_meta->m_file_index; + + return 0; +} + +int Clone_Handle::close_file(Clone_Task *task) { + bool success = true; + + /* Close file, if opened. */ + if (task->m_current_file_des.m_file != OS_FILE_CLOSED) { + success = os_file_close(task->m_current_file_des); + } + + task->m_current_file_des.m_file = OS_FILE_CLOSED; + task->m_file_cache = true; + + if (!success) { + my_error(ER_INTERNAL_ERROR, MYF(0), "Innodb error while closing file"); + return (ER_INTERNAL_ERROR); + } + + return (0); +} + +int Clone_Handle::file_callback(Ha_clone_cbk *cbk, Clone_Task *task, uint len, + bool buf_cbk, uint64_t offset +#ifdef UNIV_PFS_IO + , + const char *src_file, uint src_line +#endif /* UNIV_PFS_IO */ +) { + int err; + Ha_clone_file file; + + /* Platform specific code to set file handle */ +#ifdef _WIN32 + file.type = Ha_clone_file::FILE_HANDLE; + file.file_handle = static_cast(task->m_current_file_des.m_file); +#else + file.type = Ha_clone_file::FILE_DESC; + file.file_desc = task->m_current_file_des.m_file; +#endif /* _WIN32 */ + + /* Register for PFS IO */ +#ifdef UNIV_PFS_IO + PSI_file_locker_state state; + struct PSI_file_locker *locker; + enum PSI_file_operation psi_op; + + locker = nullptr; + psi_op = is_copy_clone() ? PSI_FILE_READ : PSI_FILE_WRITE; + + register_pfs_file_io_begin(&state, locker, task->m_current_file_des, len, + psi_op, src_file, src_line); +#endif /* UNIV_PFS_IO */ + + /* Call appropriate callback to transfer data. */ + if (is_copy_clone()) { + /* Send data from file. */ + err = cbk->file_cbk(file, len); + + } else if (buf_cbk) { + unsigned char *data_buf = nullptr; + uint32_t data_len = 0; + /* Get data buffer */ + err = cbk->apply_buffer_cbk(data_buf, data_len); + if (err == 0) { + /* Modify and write data buffer to file. */ + err = modify_and_write(task, offset, data_buf, data_len); + } + } else { + /* Write directly to file. */ + err = cbk->apply_file_cbk(file); + } + +#ifdef UNIV_PFS_IO + register_pfs_file_io_end(locker, len); +#endif /* UNIV_PFS_IO */ + + return (err); +} diff --git a/storage/innobase/clone/clone0copy.cc b/storage/innobase/clone/clone0copy.cc new file mode 100644 index 0000000000000..6372db993be55 --- /dev/null +++ b/storage/innobase/clone/clone0copy.cc @@ -0,0 +1,1634 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file clone/clone0copy.cc + Innodb copy snapshot data + + *******************************************************/ + +#include "buf0dump.h" +#include "clone0clone.h" +#include "dict0dict.h" +#include "fsp0sysspace.h" +#include "log.h" +#include "clone_handler.h" +#include "handler.h" +#include "mysqld.h" +#include "srv0start.h" +#include "trx0sys.h" + +/** Callback to add an archived redo file to current snapshot +@param[in] file_name file name +@param[in] file_size file size in bytes +@param[in] file_offset start offset in bytes +@param[in] context snapshot +@return error code */ +static int add_redo_file_callback(char *file_name, uint64_t file_size, + uint64_t file_offset, void *context) { + auto snapshot= static_cast(context); + auto err= snapshot->add_redo_file(file_name, file_size, file_offset); + return err; +} + +/** Callback to add tracked page IDs to current snapshot +@param[in] context snapshot +@param[in] buff buffer having page IDs +@param[in] num_pages number of tracked pages +@return error code */ +static int add_page_callback(void *context, byte *buff, uint num_pages) { + uint index; + Clone_Snapshot *snapshot; + + space_id_t space_id; + uint32_t page_num; + + snapshot = static_cast(context); + + /* Extract the page Ids from the buffer. */ + for (index = 0; index < num_pages; index++) { + space_id = mach_read_from_4(buff); + buff += 4; + + page_num = mach_read_from_4(buff); + buff += 4; + + auto err = snapshot->add_page(space_id, page_num); + + if (err != 0) { + return (err); + } + } + + return (0); +} + +int Clone_Snapshot::add_buf_pool_file() { + char path[OS_FILE_MAX_PATH]; + /* Generate the file name. */ + buf_dump_generate_path(path, sizeof(path)); + + os_file_type_t type; + bool exists= false; + + bool ret= os_file_status(path, &exists, &type); + + if (!ret || !exists) + return 0; + + auto file_size = os_file_get_size(path); + auto size_bytes = file_size.m_total_size; + + /* Check for error */ + if (size_bytes == static_cast(~0)) { + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_CANT_OPEN_FILE, MYF(0), path, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return ER_CANT_OPEN_FILE; + } + /* Always the first file in list */ + ut_ad(num_data_files() == 0); + + m_data_bytes_disk += size_bytes; + m_monitor.add_estimate(size_bytes); + + return add_file(path, size_bytes, size_bytes, nullptr, false); +} + +int Clone_Snapshot::init_redo_archiving() { + ut_ad(m_snapshot_type != HA_CLONE_BLOCKING); + + if (m_snapshot_type == HA_CLONE_BLOCKING) { + /* We are not supposed to start redo archiving in this mode. */ + m_redo_file_size = m_redo_header_size = m_redo_trailer_size = 0; + return ER_INTERNAL_ERROR; /* purecov: inspected */ + } + + /* If not blocking clone, allocate redo header and trailer buffer. */ + + m_redo_ctx.get_header_size(m_redo_header_size, m_redo_trailer_size); + + m_redo_header = static_cast(mem_heap_zalloc( + m_snapshot_heap, + m_redo_header_size + m_redo_trailer_size + OS_FILE_LOG_BLOCK_SIZE)); + + if (m_redo_header == nullptr) { + /* purecov: begin inspected */ + my_error(ER_OUTOFMEMORY, MYF(0), m_redo_header_size + m_redo_trailer_size); + + return ER_OUTOFMEMORY; + /* purecov: end */ + } + + m_redo_header = + static_cast(ut_align(m_redo_header, OS_FILE_LOG_BLOCK_SIZE)); + + m_redo_trailer = m_redo_header + m_redo_header_size; + + /* Start Redo Archiving */ + const int err = m_redo_ctx.start(m_redo_header, m_redo_header_size); + + if (err != 0) { + m_redo_file_size = 0; + return err; /* purecov: inspected */ + } + + m_redo_file_size = uint64_t{m_redo_ctx.get_archived_file_size()}; + ut_ad(m_redo_file_size >= LOG_FILE_MIN_SIZE); + + if (m_redo_file_size < LOG_FILE_MIN_SIZE) { + my_error(ER_INTERNAL_ERROR, MYF(0)); + return ER_INTERNAL_ERROR; /* purecov: inspected */ + } + + return 0; +} + +#ifdef UNIV_DEBUG +void Clone_Snapshot::debug_wait_state_transit() { + mysql_mutex_assert_owner(&m_snapshot_mutex); + + /* Allow DDL to enter and check. */ + mysql_mutex_unlock(&m_snapshot_mutex); + + DEBUG_SYNC_C("clone_state_transit_file_copy"); + + mysql_mutex_lock(&m_snapshot_mutex); +} +#endif /* UNIV_DEBUG */ + +int Clone_Snapshot::init_file_copy(Snapshot_State new_state) { + ut_ad(m_snapshot_handle_type == CLONE_HDL_COPY); + + State_transit transit_guard(this, new_state); + + int err = transit_guard.get_error(); + + if (err != 0) { + return err; /* purecov: inspected */ + } + + ut_d(debug_wait_state_transit()); + + m_monitor.init_state(srv_stage_clone_file_copy.m_key, m_enable_pfs); + + if (m_snapshot_type == HA_CLONE_BLOCKING) { + /* In HA_CLONE_BLOCKING mode we treat redo files as usual files. + We need to clear these special treatment not to count them twice. */ + m_redo_file_size = m_redo_header_size = m_redo_trailer_size = 0; + + } else if (m_snapshot_type == HA_CLONE_REDO) { + err = init_redo_archiving(); + + } else if (m_snapshot_type == HA_CLONE_HYBRID || + m_snapshot_type == HA_CLONE_PAGE) { + /* Start modified Page ID Archiving */ + err = m_page_ctx.start(false, nullptr); + } else { + ut_ad(m_snapshot_type == HA_CLONE_BLOCKING); + err = ER_INTERNAL_ERROR; + } + + if (err != 0) { + return err; /* purecov: inspected */ + } + + /* Initialize estimation about on disk bytes. */ + init_disk_estimate(); + + /* Add buffer pool dump file. Always the first one in the list. */ + err = add_buf_pool_file(); + + if (err != 0) { + return err; /* purecov: inspected */ + } + + /* Iterate all tablespace files and add persistent data files. */ + auto error = Fil_iterator::for_each_file( + [&](fil_node_t *file) { return (add_node(file, false)); }); + + if (error != DB_SUCCESS) { + return ER_INTERNAL_ERROR; /* purecov: inspected */ + } + + ib::info() + << "Clone State FILE COPY : " << m_num_current_chunks << " chunks, " + << " chunk size : " << (chunk_size() * UNIV_PAGE_SIZE) / (1024 * 1024) + << " M"; + + m_monitor.change_phase(); + return 0; +} + +int Clone_Snapshot::init_page_copy(Snapshot_State new_state, byte *page_buffer, + uint page_buffer_len) { + ut_ad(m_snapshot_handle_type == CLONE_HDL_COPY); + + State_transit transit_guard(this, new_state); + + int err = transit_guard.get_error(); + + if (err != 0) { + return err; /* purecov: inspected */ + } + + m_monitor.init_state(srv_stage_clone_page_copy.m_key, m_enable_pfs); + + if (m_snapshot_type == HA_CLONE_HYBRID) { + /* Start Redo Archiving */ + err = init_redo_archiving(); + + } else if (m_snapshot_type == HA_CLONE_PAGE) { + /* Start COW for all modified pages - Not implemented. */ + ut_d(ut_error); + } else { + ut_d(ut_error); + } + + if (err != 0) { + /* purecov: begin inspected */ + m_page_ctx.release(); + return err; + /* purecov: end */ + } + + /* Stop modified page archiving. */ + err = m_page_ctx.stop(nullptr); + + DEBUG_SYNC_C("clone_stop_page_archiving_without_releasing"); + + if (err != 0) { + /* purecov: begin inspected */ + m_page_ctx.release(); + return err; + /* purecov: end */ + } + + /* Iterate all tablespace files and add new data files created. */ + auto error = Fil_iterator::for_each_file( + [&](fil_node_t *file) { return add_node(file, true); }); + + if (error != DB_SUCCESS) { + return ER_INTERNAL_ERROR; /* purecov: inspected */ + } + + /* Collect modified page Ids from Page Archiver. */ + void *context; + uint aligned_size; + + context = static_cast(this); + + /* Check pages added for encryption. */ + auto num_pages_encryption = m_page_set.size(); + + if (num_pages_encryption > 0) { + m_monitor.add_estimate(num_pages_encryption * UNIV_PAGE_SIZE); + } + + err = m_page_ctx.get_pages(add_page_callback, context, page_buffer, + page_buffer_len); + + m_page_vector.assign(m_page_set.begin(), m_page_set.end()); + + aligned_size = ut_calc_align(m_num_pages, chunk_size()); + m_num_current_chunks = aligned_size >> m_chunk_size_pow2; + + ib::info() + << "Clone State PAGE COPY : " << m_num_pages << " pages, " + << m_num_duplicate_pages << " duplicate pages, " << m_num_current_chunks + << " chunks, " + << " chunk size : " << (chunk_size() * UNIV_PAGE_SIZE) / (1024 * 1024) + << " M"; + m_page_ctx.release(); + + m_monitor.change_phase(); + return err; +} + +int Clone_Snapshot::init_redo_copy() { + ut_ad(m_snapshot_handle_type == CLONE_HDL_COPY); + ut_ad(m_snapshot_type != HA_CLONE_BLOCKING); + + /* Start transition to next state. */ + State_transit transit_guard(this, CLONE_SNAPSHOT_REDO_COPY); + + /* Stop redo archiving even on error. */ + auto redo_error = m_redo_ctx.stop(m_redo_trailer, m_redo_trailer_size, + m_redo_trailer_offset); + + if (redo_error != 0) { + return redo_error; /* purecov: inspected */ + } + + int transit_error = transit_guard.get_error(); + + if (transit_error != 0) { + return transit_error; /* purecov: inspected */ + } + + m_monitor.init_state(srv_stage_clone_redo_copy.m_key, m_enable_pfs); + + /* Iterate all tablespace files and add new data files created. */ + auto error = Fil_iterator::for_each_file( + [&](fil_node_t *file) { return add_node(file, true); }); + + if (error != DB_SUCCESS) { + return ER_INTERNAL_ERROR; /* purecov: inspected */ + } + + /* Collect archived redo log files from Log Archiver. */ + auto context = static_cast(this); + + redo_error = m_redo_ctx.get_files(add_redo_file_callback, context); + + /* Add another chunk for the redo log header. */ + ++m_num_redo_chunks; + + m_monitor.add_estimate(m_redo_header_size); + + /* Add another chunk for the redo log trailer. */ + ++m_num_redo_chunks; + + if (m_redo_trailer_size != 0) { + m_monitor.add_estimate(m_redo_trailer_size); + } + + m_num_current_chunks = m_num_redo_chunks; + + ib::info() + << "Clone State REDO COPY : " << m_num_current_chunks << " chunks, " + << " chunk size : " << (chunk_size() * UNIV_PAGE_SIZE) / (1024 * 1024) + << " M"; + + m_monitor.change_phase(); + return redo_error; +} + +bool Clone_Snapshot::build_file_name(Clone_File_Meta *file_meta, + const char *file_name) { + size_t new_len = strlen(file_name) + 1; + auto new_name = const_cast(file_meta->m_file_name); + + /* Check if allocation required. */ + if (new_len <= file_meta->m_file_name_alloc_len) { + strcpy(new_name, file_name); + file_meta->m_file_name_len = new_len; + return true; + } + + if (new_len > FN_REFLEN_SE) { + /* purecov: begin deadcode */ + my_error(ER_PATH_LENGTH, MYF(0), "CLONE FILE NAME"); + ut_d(ut_error); + return false; + /* purecov: end */ + } + + size_t alloc_len = new_len; + + /* For reallocation, allocate in multiple of base size to avoid frequent + allocation by rename DDL. */ + if (file_meta->m_file_name_alloc_len > 0) { + alloc_len = ut_calc_align(new_len, S_FILE_NAME_BASE_LEN); + } + + new_name = static_cast(mem_heap_zalloc(m_snapshot_heap, alloc_len)); + + if (new_name == nullptr) { + /* purecov: begin inspected */ + my_error(ER_OUTOFMEMORY, MYF(0), static_cast(alloc_len)); + return false; + /* purecov: end */ + } + + strcpy(new_name, file_name); + + file_meta->m_file_name = new_name; + file_meta->m_file_name_len = new_len; + file_meta->m_file_name_alloc_len = alloc_len; + + return true; +} + +Clone_file_ctx *Clone_Snapshot::build_file(const char *file_name, + uint64_t file_size, + uint64_t file_offset, + uint &num_chunks) { + /* Allocate for file metadata from snapshot heap. */ + uint64_t aligned_size = sizeof(Clone_file_ctx); + + auto file_ctx = static_cast( + mem_heap_alloc(m_snapshot_heap, static_cast(aligned_size))); + + if (file_ctx == nullptr) { + /* purecov: begin inspected */ + my_error(ER_OUTOFMEMORY, MYF(0), static_cast(aligned_size)); + return nullptr; + /* purecov: end */ + } + + file_ctx->init(Clone_file_ctx::Extension::NONE); + + auto file_meta = file_ctx->get_file_meta(); + + /* For redo file with no data, add dummy entry. */ + if (file_name == nullptr) { + num_chunks = 1; + + file_meta->m_begin_chunk = 1; + file_meta->m_end_chunk = 1; + + return file_ctx; + } + + file_meta->m_file_size = file_size; + + /* reduce offset amount from total size */ + ut_ad(file_size >= file_offset); + file_size -= file_offset; + + /* Calculate and set chunk parameters. */ + uint64_t size_in_pages = ut_uint64_align_up(file_size, UNIV_PAGE_SIZE); + size_in_pages /= UNIV_PAGE_SIZE; + + aligned_size = ut_uint64_align_up(size_in_pages, chunk_size()); + + num_chunks = static_cast(aligned_size >> m_chunk_size_pow2); + + file_meta->m_begin_chunk = m_num_current_chunks + 1; + file_meta->m_end_chunk = m_num_current_chunks + num_chunks; + + bool success = build_file_name(file_meta, file_name); + + return success ? file_ctx : nullptr; +} + +bool Clone_Snapshot::file_ctx_changed(const fil_node_t *node, + Clone_file_ctx *&file_ctx) { + file_ctx = nullptr; + + auto space = node->space; + auto count = m_data_file_map.count(space->id); + + /* This is a new file after clone has started. */ + if (count == 0) { + return true; + } + auto file_index = m_data_file_map[space->id]; + + if (file_index == 0) { + /* purecov: begin deadcode */ + ut_d(ut_error); + return true; + /* purecov: end */ + } + + /* File descriptor already exists. */ + --file_index; + auto num_data_files = m_data_file_vector.size(); + + ut_ad(file_index < num_data_files); + + if (file_index < num_data_files) { + file_ctx = m_data_file_vector[file_index]; + } + + if (file_ctx == nullptr) { + /* purecov: begin deadcode */ + ut_d(ut_error); + return false; + /* purecov: end */ + } + + ut_ad(!file_ctx->modifying()); + + bool file_changed = + file_ctx->m_state.load() != Clone_file_ctx::State::CREATED; + + /* Consider file modification only from previous state. */ + if (file_changed && file_ctx->by_ddl(get_state())) { + return true; + } + + /* The file is not modified in previous state. Next we consider + encryption or compression type changes. The information is not + useful for "redo copy" state. Such changes would be applied + during redo recovery. */ + if (get_state() == CLONE_SNAPSHOT_REDO_COPY) { + return false; + } + + const auto file_meta = file_ctx->get_file_meta_read(); + + /* Check if encryption property has changed. */ + if (file_meta->can_encrypt() != space->is_encrypted()) + return true; + + /* Check if compression property has changed. */ + if (file_meta->can_compress() != space->is_compressed()) + return true; + + return false; +} + +int Clone_Snapshot::add_file(const char *name, uint64_t size_bytes, + uint64_t alloc_bytes, fil_node_t *node, + bool by_ddl) { + ut_ad(m_snapshot_handle_type == CLONE_HDL_COPY); + + Clone_file_ctx *file_ctx = nullptr; + + /* Check if ddl has modified this space. */ + if (by_ddl && !file_ctx_changed(node, file_ctx)) { + return 0; + } + + if (file_ctx == nullptr) { + uint32_t num_chunks = 0; + /* Build file metadata entry and add to data file vector. */ + file_ctx = build_file(name, size_bytes, 0, num_chunks); + + if (file_ctx == nullptr) { + return (ER_OUTOFMEMORY); /* purecov: inspected */ + } + auto file_meta = file_ctx->get_file_meta(); + + file_meta->m_alloc_size = alloc_bytes; + file_meta->m_file_index = static_cast(num_data_files()); + m_data_file_vector.push_back(file_ctx); + + if (!by_ddl) { + /* Update total number of chunks. */ + m_num_data_chunks += num_chunks; + m_num_current_chunks = m_num_data_chunks; + } + } + + /* All done if not a space file node like buffer pool dump file. */ + if (node == nullptr) { + return 0; + } + + auto file_meta = file_ctx->get_file_meta(); + + /* Update maximum file name length in snapshot. */ + if (file_meta->m_file_name_len > m_max_file_name_len) { + m_max_file_name_len = static_cast(file_meta->m_file_name_len); + } + + ut_ad(file_meta->m_deleted == file_ctx->deleted()); + ut_ad(file_meta->m_renamed == file_ctx->renamed()); + + if (by_ddl) { + file_ctx->set_ddl(get_state()); + + /* Rebuild file name for renamed descriptor. */ + if (file_ctx->renamed()) { + build_file_name(file_meta, name); + } + } + + /* Set space ID, compression and encryption attribute */ + auto space = node->space; + file_meta->m_space_id = space->id; + file_meta->m_is_compressed= space->is_compressed(); + file_meta->m_fsp_flags = static_cast(space->flags); + file_meta->m_punch_hole = node->punch_hole; + file_meta->m_fsblk_size = node->block_size; + + file_meta->m_is_encrypted= space->is_encrypted(); + /* TOD0: File metadata: Encryption information */ + // file_meta->m_encryption_metadata = space->m_encryption_metadata; + + /* Modify file meta encryption flag if space encryption or decryption + already started. This would allow clone to send pages accordingly + during page copy and persist the flag. */ + /* TODO: Handle Encrypt/Un-Encrypt DDL via notification infrastructure. */ + // if (space->encryption_op_in_progress == Encryption::Progress::DECRYPTION) { + // fsp_flags_unset_encryption(file_meta->m_fsp_flags); + + // } else if (space->encryption_op_in_progress == + // Encryption::Progress::ENCRYPTION) { + // fsp_flags_set_encryption(file_meta->m_fsp_flags); + // } + + bool is_redo_copy = (get_state() == CLONE_SNAPSHOT_REDO_COPY); + + if (file_meta->can_encrypt()) { + /* All encrypted files created during PAGE_COPY state have their keys redo + logged. The recovered keys from redo log are encrypted by donor master key + and cannot be used is recipient. We send the keys explicitly in this case + to be encrypted by recipient with its own master key. + + NOTE: encrypted files created during FILE_COPY state don't have this issue + as page-0 is always sent during PAGE_COPY with unencrypted data file key. + + We always check for SSL connection before sending keys for encrypted tables + and error out for security. */ + if (is_redo_copy && by_ddl) { + file_meta->m_transfer_encryption_key = true; + } + } + + /* Add to hash map only for first node of the tablespace. */ + auto space_id = file_meta->m_space_id; + + if (m_data_file_map[space_id] == 0) { + m_data_file_map[space_id] = file_meta->m_file_index + 1; + } + + return 0; +} + +dberr_t Clone_Snapshot::add_node(fil_node_t *node, bool by_ddl) { + ut_ad(m_snapshot_handle_type == CLONE_HDL_COPY); + + auto space = node->space; + if (space->is_encrypted() || space->is_compressed()) + { + my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Encrypted and compressed " + "tablespace "); + return DB_ERROR; + } + bool is_page_copy = (get_state() == CLONE_SNAPSHOT_PAGE_COPY); + + if (by_ddl && is_page_copy && space->is_encrypted()) { + /* Add page 0 always for encrypted tablespace. */ + Clone_Page page_zero; + page_zero.m_space_id = space->id; + page_zero.m_page_no = 0; + m_page_set.insert(page_zero); + ++m_num_pages; + } + + /* For compressed pages the file size doesn't match + physical page size multiplied by number of pages. It is + because we use UNIV_PAGE_SIZE while creating the node + and tablespace. */ + auto file_size = os_file_get_size(node->name); + auto size_bytes = file_size.m_total_size; + auto alloc_size = file_size.m_alloc_size; + + /* Check for error */ + if (size_bytes == static_cast(~0)) { + char errbuf[MYSYS_STRERROR_SIZE]; + my_error(ER_CANT_OPEN_FILE, MYF(0), node->name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return (DB_ERROR); + } + + /* Update estimation */ + if (!by_ddl) { + m_data_bytes_disk += alloc_size; + m_monitor.add_estimate(size_bytes); + } + + /* Add file to snapshot. */ + auto err = add_file(node->name, size_bytes, alloc_size, node, by_ddl); + + return (err != 0 ? DB_ERROR : DB_SUCCESS); +} + +int Clone_Snapshot::add_page(space_id_t space_id, uint32_t page_num) { + /* Skip pages belonging to tablespace not included for clone. This could + be some left over pages from drop or truncate in buffer pool which + would eventually get removed. Or it may be a page for an undo tablespace + that was deleted with BUF_REMOVE_NONE. */ + auto count = m_data_file_map.count(space_id); + if (count == 0) { + return (0); + } + + Clone_Page cur_page; + cur_page.m_space_id = space_id; + cur_page.m_page_no = page_num; + + auto result = m_page_set.insert(cur_page); + + if (result.second) { + m_num_pages++; + m_monitor.add_estimate(UNIV_PAGE_SIZE); + } else { + m_num_duplicate_pages++; + } + + return (0); +} + +int Clone_Snapshot::add_redo_file(char *file_name, uint64_t file_size, + uint64_t file_offset) { + ut_ad(m_snapshot_handle_type == CLONE_HDL_COPY); + + uint num_chunks; + + /* Build redo file metadata and add to redo vector. */ + auto file_ctx = build_file(file_name, file_size, file_offset, num_chunks); + if (file_ctx == nullptr) { + return (ER_OUTOFMEMORY); + } + + auto file_meta = file_ctx->get_file_meta(); + m_monitor.add_estimate(file_meta->m_file_size - file_offset); + + /* Set the start offset for first redo file. This could happen + if redo archiving was already in progress, possibly by another + concurrent snapshot. */ + if (num_redo_files() == 0) { + m_redo_start_offset = file_offset; + } else { + ut_ad(file_offset == log_t::START_OFFSET); + } + + file_meta->m_alloc_size = 0; + + file_meta->m_space_id= SRV_SPACE_ID_UPPER_BOUND; + file_meta->m_is_compressed= false; + /* TOD0: File metadata: Encryption information */ + // file_meta->m_encryption_metadata = log_sys->m_encryption_metadata; + file_meta->m_fsp_flags = ULINT32_UNDEFINED; + file_meta->m_punch_hole = false; + file_meta->m_fsblk_size = 0; + + file_meta->m_file_index = static_cast(num_redo_files()); + + m_redo_file_vector.push_back(file_ctx); + + m_num_redo_chunks += num_chunks; + m_num_current_chunks = m_num_redo_chunks; + + /* In rare case of small redo file, large concurrent DMLs and + slow data transfer. Currently we support maximum 1k redo files. */ + if (num_redo_files() > LOG_FILE_MAX_NUM) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "More than %zu archived redo files. Please retry clone.", + LOG_FILE_MAX_NUM); + return ER_INTERNAL_ERROR; + } + + return (0); +} + +int Clone_Handle::send_task_metadata(Clone_Task *task, Ha_clone_cbk *callback) { + Clone_Desc_Task_Meta task_desc; + + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + /* Build task descriptor with metadata */ + task_desc.init_header(get_version()); + task_desc.m_task_meta = task->m_task_meta; + + auto desc_len = task->m_alloc_len; + task_desc.serialize(task->m_serial_desc, desc_len, nullptr); + + callback->set_data_desc(task->m_serial_desc, desc_len); + callback->clear_flags(); + callback->set_ack(); + + auto err = callback->buffer_cbk(nullptr, 0); + + return (err); +} + +int Clone_Handle::send_keep_alive(Clone_Task *task, Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + Clone_Desc_State state_desc; + state_desc.init_header(get_version()); + + /* Build state descriptor from snapshot and task */ + auto snapshot = m_clone_task_manager.get_snapshot(); + snapshot->get_state_info(false, &state_desc); + + state_desc.m_is_ack = true; + + auto task_meta = &task->m_task_meta; + state_desc.m_task_index = task_meta->m_task_index; + + auto desc_len = task->m_alloc_len; + state_desc.serialize(task->m_serial_desc, desc_len, nullptr); + + callback->set_data_desc(task->m_serial_desc, desc_len); + callback->clear_flags(); + + auto err = callback->buffer_cbk(nullptr, 0); + + return (err); +} + +int Clone_Handle::send_state_metadata(Clone_Task *task, Ha_clone_cbk *callback, + bool is_start) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + /* Before starting state, check and send any new metadata added by DDL. */ + if (is_start) { + auto err = send_all_ddl_metadata(task, callback); + if (err != 0) { + return err; /* purecov: inspected */ + } + } + + Clone_Desc_State state_desc; + state_desc.init_header(get_version()); + + /* Build state descriptor from snapshot and task */ + auto snapshot = m_clone_task_manager.get_snapshot(); + + /* Master needs to send estimation while beginning state */ + auto get_estimate = (task->m_is_master && is_start); + + snapshot->get_state_info(get_estimate, &state_desc); + + /* Indicate if it is the end of state */ + state_desc.m_is_start = is_start; + + /* Check if remote has already acknowledged state transfer */ + if (!is_start && task->m_is_master && + !m_clone_task_manager.check_ack(&state_desc)) { + ut_ad(task->m_is_master); + ut_ad(m_clone_task_manager.is_restarted()); + + ib::info() + << "CLONE COPY: Skip ACK after restart for state " + << state_desc.m_state; + return (0); + } + + auto task_meta = &task->m_task_meta; + state_desc.m_task_index = task_meta->m_task_index; + + auto desc_len = task->m_alloc_len; + state_desc.serialize(task->m_serial_desc, desc_len, nullptr); + + callback->set_data_desc(task->m_serial_desc, desc_len); + callback->clear_flags(); + callback->set_ack(); + + auto err = callback->buffer_cbk(nullptr, 0); + + if (err != 0) { + return (err); + } + + if (is_start) { + /* Send all file metadata while starting state */ + err = send_all_file_metadata(task, callback); + + } else { + /* Wait for ACK while finishing state */ + err = m_clone_task_manager.wait_ack(this, task, callback); + } + + return (err); +} + +int Clone_Handle::send_all_file_metadata(Clone_Task *task, + Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + if (!task->m_is_master) { + return 0; + } + + DEBUG_SYNC_C("clone_before_init_meta"); + + auto snapshot = m_clone_task_manager.get_snapshot(); + bool is_redo = (snapshot->get_state() == CLONE_SNAPSHOT_REDO_COPY); + + /* Send all file metadata for data/redo files */ + auto err = snapshot->iterate_files([&](Clone_file_ctx *file_ctx) { + auto file_meta = file_ctx->get_file_meta(); + /* While sending initial metadata, reset the DDL state so that + recipient could create the files as new file. */ + Clone_File_Meta local_meta = *file_meta; + local_meta.reset_ddl(); + + auto err_file = send_file_metadata(task, &local_meta, is_redo, callback); + return err_file; + }); + + return err; +} + +int Clone_Handle::send_file_metadata(Clone_Task *task, + const Clone_File_Meta *file_meta, + bool is_redo, Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + auto snapshot = m_clone_task_manager.get_snapshot(); + + Clone_Desc_File_MetaData file_desc; + + file_desc.m_file_meta = *file_meta; + file_desc.m_state = snapshot->get_state(); + + if (is_redo) + { + /* For Redo log always send the fixed redo file size. */ + file_desc.m_file_meta.m_file_size = snapshot->get_redo_file_size(); + + file_desc.m_file_meta.m_file_name = nullptr; + file_desc.m_file_meta.m_file_name_len = 0; + file_desc.m_file_meta.m_file_name_alloc_len = 0; + + } + else if (file_meta->m_space_id == UINT32_MAX) + { + /* Server buffer dump file ib_buffer_pool. */ + ut_ad(file_desc.m_state == CLONE_SNAPSHOT_FILE_COPY); + ut_ad(file_meta->m_file_index == 0); + + file_desc.m_file_meta.m_file_name = SRV_BUF_DUMP_FILENAME_DEFAULT; + + file_desc.m_file_meta.m_file_name_len = + static_cast(strlen(SRV_BUF_DUMP_FILENAME_DEFAULT)) + 1; + + file_desc.m_file_meta.m_file_name_alloc_len = 0; + + } + else if (file_meta->m_space_id == TRX_SYS_SPACE + || srv_is_undo_tablespace(file_meta->m_space_id)) + { + /* For system tablespace, remove path. */ + auto name_ptr = strrchr(file_meta->m_file_name, OS_PATH_SEPARATOR); + + if (name_ptr != nullptr) + { + name_ptr++; + + file_desc.m_file_meta.m_file_name = name_ptr; + file_desc.m_file_meta.m_file_name_len = + static_cast(strlen(name_ptr)) + 1; + } + file_desc.m_file_meta.m_file_name_alloc_len = 0; + } + + file_desc.init_header(get_version()); + + auto desc_len = task->m_alloc_len; + file_desc.serialize(task->m_serial_desc, desc_len, nullptr); + + callback->set_data_desc(task->m_serial_desc, desc_len); + callback->clear_flags(); + + /* Check for secure transfer for encrypted table. */ + if (file_meta->can_encrypt() || srv_encrypt_tables || srv_encrypt_log) + callback->set_secure(); + + auto err = callback->buffer_cbk(nullptr, 0); + + return (err); +} + +int Clone_Handle::send_data(Clone_Task *task, const Clone_file_ctx *file_ctx, + uint64_t offset, byte *buffer, uint32_t size, + uint64_t new_file_size, Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + auto snapshot = m_clone_task_manager.get_snapshot(); + auto file_meta = file_ctx->get_file_meta_read(); + + /* Build data descriptor */ + Clone_Desc_Data data_desc; + data_desc.init_header(get_version()); + data_desc.m_state = snapshot->get_state(); + + data_desc.m_task_meta = task->m_task_meta; + + data_desc.m_file_index = file_meta->m_file_index; + data_desc.m_data_len = size; + data_desc.m_file_offset = offset; + data_desc.m_file_size = file_meta->m_file_size; + + /* Adjust file size to extend automatically while copying page 0. */ + if (new_file_size > data_desc.m_file_size) { + ut_ad(snapshot->get_state() == CLONE_SNAPSHOT_PAGE_COPY); + ut_ad(offset == 0); + data_desc.m_file_size = new_file_size; + } + + /* Serialize data descriptor and set in callback */ + auto desc_len = task->m_alloc_len; + data_desc.serialize(task->m_serial_desc, desc_len, nullptr); + + callback->set_data_desc(task->m_serial_desc, desc_len); + callback->clear_flags(); + + auto file_type = OS_CLONE_DATA_FILE; + bool is_log_file = (data_desc.m_state == CLONE_SNAPSHOT_REDO_COPY); + + if (is_log_file || file_meta->m_space_id == UINT32_MAX) { + file_type = OS_CLONE_LOG_FILE; + } + + int err = 0; + + if (buffer != nullptr) { + /* Send data from buffer. */ + err = callback->buffer_cbk(buffer, size); + + } else { + /* Send data from file. */ + if (task->m_current_file_des.m_file == OS_FILE_CLOSED) { + File_init_cbk empty_cbk; + err = open_file(task, file_ctx, file_type, false, empty_cbk); + + if (err != 0) { + return (err); + } + } + + ut_ad(task->m_current_file_index == file_meta->m_file_index); + + os_file_t file_hdl; + char errbuf[MYSYS_STRERROR_SIZE]; + + file_hdl = task->m_current_file_des.m_file; + auto success = os_file_seek(nullptr, file_hdl, offset); + + if (!success) { + my_error(ER_ERROR_ON_READ, MYF(0), file_meta->m_file_name, errno, + my_strerror(errbuf, sizeof(errbuf), errno)); + return (ER_ERROR_ON_READ); + } + + if (task->m_file_cache) { + callback->set_os_buffer_cache(); + /* For data file recommend zero copy for cached IO. */ + if (!is_log_file) { + callback->set_zero_copy(); + } + } + + callback->set_source_name(file_meta->m_file_name); + + err = file_callback(callback, task, size, false, offset +#ifdef UNIV_PFS_IO + , __FILE__, __LINE__ +#endif /* UNIV_PFS_IO */ + ); + } + + task->m_data_size += size; + + return (err); +} + +void Clone_Handle::display_progress( + uint32_t cur_chunk, uint32_t max_chunk, uint32_t &percent_done, + std::chrono::steady_clock::time_point &disp_time) { + auto current_time = std::chrono::steady_clock::now(); + auto current_percent = (cur_chunk * 100) / max_chunk; + + if (current_percent >= percent_done + 20 || + (current_time - disp_time > std::chrono::seconds{5} && + current_percent > percent_done)) { + percent_done = current_percent; + disp_time = current_time; + + ib::info() + << "Stage progress: " << percent_done << "% completed."; + } +} + +int Clone_Handle::snapshot() +{ + ib::info() << "Clone State BEGIN REDO COPY"; + auto snapshot = m_clone_task_manager.get_snapshot(); + int err = snapshot->init_redo_copy(); + if (err != 0) { + return err; + } + m_clone_task_manager.reinit_state(); + return 0; +} + +int Clone_Handle::copy(uint task_id, Ha_clone_cbk *callback, + bool post_snapshot) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + Snapshot_State final_state = post_snapshot ? CLONE_SNAPSHOT_DONE : + CLONE_SNAPSHOT_REDO_COPY; + Snapshot_State cur_state = m_clone_task_manager.get_state(); + if (cur_state >= final_state) { + return 0; + } + ut_ad(post_snapshot || cur_state < CLONE_SNAPSHOT_REDO_COPY); + ut_ad(!post_snapshot || cur_state == CLONE_SNAPSHOT_REDO_COPY); + + /* Get task from task manager. */ + auto task = m_clone_task_manager.get_task_by_index(task_id); + + auto err = m_clone_task_manager.alloc_buffer(task); + if (err != 0) { + return (err); + } + + /* Allow restart only after copy is started. Disallow restart during redo + log copy. */ + m_allow_restart = !post_snapshot; + + /* Send the task metadata. */ + if (!post_snapshot) { + err = send_task_metadata(task, callback); + } + if (err != 0) { + return (err); + } + + auto send_matadata = m_clone_task_manager.is_restart_metadata(task); + + /* Send state metadata to remote during restart */ + if (send_matadata) { + ut_ad(task->m_is_master); + ut_ad(m_clone_task_manager.is_restarted()); + + err = send_state_metadata(task, callback, true); + + /* Send all file metadata during restart */ + } else if (task->m_is_master && + m_clone_task_manager.get_state() == CLONE_SNAPSHOT_FILE_COPY && + !m_clone_task_manager.is_file_metadata_transferred()) { + ut_ad(m_clone_task_manager.is_restarted()); + err = send_all_file_metadata(task, callback); + } else if (post_snapshot) { + err = send_state_metadata(task, callback, true); + } + + if (err != 0) { + return (err); + } + /* Adjust block size based on client buffer size. */ + auto snapshot = m_clone_task_manager.get_snapshot(); + snapshot->update_block_size(callback->get_client_buffer_size()); + + auto max_chunks = snapshot->get_num_chunks(); + + /* Set time values for tracking stage progress. */ + + auto disp_time = std::chrono::steady_clock::now(); + + /* Loop and process data until snapshot is moved to DONE state. */ + uint32_t percent_done = 0; + + while (m_clone_task_manager.get_state() != final_state) { + /* Reserve next chunk for current state from snapshot. */ + uint32_t current_chunk = 0; + uint32_t current_block = 0; + + err = m_clone_task_manager.reserve_next_chunk(task, current_chunk, + current_block); + + if (err != 0) { + break; + } + + if (current_chunk != 0) { + /* Send blocks from the reserved chunk. */ + err = process_chunk(task, current_chunk, current_block, callback); + + /* Display stage progress based on % completion. */ + if (task->m_is_master) { + display_progress(current_chunk, max_chunks, percent_done, disp_time); + } + + } else { + /* No more chunks in current state. Transit to next state. */ + + /* Close the last open file before proceeding to next state */ + err = close_and_unpin_file(task); + + if (err != 0) { + break; + } + + /* Inform that the data transfer for current state + is over before moving to next state. The remote + needs to send back state transfer ACK for the state + transfer to complete. */ + err = send_state_metadata(task, callback, false); + + if (err != 0) { + break; + } + + /* Next state is decided by snapshot for Copy. */ + err = move_to_next_state(task, callback, nullptr); + + ut_d(task->m_ignore_sync = false); + + cur_state = m_clone_task_manager.get_state(); + /* Before snapshot state, we need to exit immediately after moving to + redo copy state. The metadata can be sent only after redo archiving is + stopped i.e. after snapshot is taken. */ + if (err != 0 || (!post_snapshot && cur_state == final_state)) { + break; + } + max_chunks = snapshot->get_num_chunks(); + percent_done = 0; + disp_time = std::chrono::steady_clock::now(); + + /* Send state metadata before processing chunks. */ + err = send_state_metadata(task, callback, true); + } + + if (err != 0) { + break; + } + } + + /* Close the last open file. */ + auto err2 = close_and_unpin_file(task); + + if (err == 0) { + err = err2; + } + + return (err); +} + +int Clone_Handle::process_chunk(Clone_Task *task, uint32_t chunk_num, + uint32_t block_num, Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + auto &task_meta = task->m_task_meta; + + /* If chunks are in increasing order, optimize file + search by index */ + uint32_t file_index_hint = 0; + + if (task_meta.m_chunk_num <= chunk_num) { + file_index_hint = task->m_current_file_index; + } + + auto state = m_clone_task_manager.get_state(); + bool is_page_copy = (state == CLONE_SNAPSHOT_PAGE_COPY); + bool is_redo_copy = (state == CLONE_SNAPSHOT_REDO_COPY); + + /* Except for page copy, file remains same for all blocks of a chunk. */ + auto snapshot = m_clone_task_manager.get_snapshot(); + + const auto *file_ctx = + snapshot->get_file_ctx(chunk_num, block_num, file_index_hint); + + const Clone_File_Meta *file_meta = nullptr; + + /* For page copy, file context is null if current chunk is over. */ + ut_ad(is_page_copy || file_ctx != nullptr); + + if (file_ctx != nullptr) { + file_meta = file_ctx->get_file_meta_read(); + } + + /* Loop over all the blocks of current chunk and send data. */ + int err = 0; + + uint32_t pin_loop_count = 0; + uint32_t unpin_count = snapshot->get_max_blocks_pin(); + + while (err == 0) { + /* For page copy same chunk might have pages from different files. */ + if (is_page_copy) { + file_ctx = snapshot->get_file_ctx(chunk_num, block_num, 0); + if (file_ctx == nullptr) { + /* Already handled all pages. */ + break; + } + file_meta = file_ctx->get_file_meta_read(); + } + + bool pins_other; + bool pins_current; + + std::tie(pins_current, pins_other) = pins_file(task, file_ctx); + + /* Let any waiting DDL file operation to proceed after handling a + set of data blocks. */ + bool allow_ddl = + snapshot->blocks_clone(file_ctx) && (pin_loop_count >= unpin_count); + + if (pins_other || (pins_current && allow_ddl)) { + err = close_and_unpin_file(task); + if (err != 0) { + break; /* purecov: inspected */ + } + pin_loop_count = 0; + } + + bool delete_action = false; + + auto mutable_ctx = const_cast(file_ctx); + + err = check_and_pin_file(task, mutable_ctx, delete_action); + + if (err != 0) { + break; /* purecov: inspected */ + } + + ++pin_loop_count; + + /* Check if file is already deleted. */ + if (file_ctx->deleted()) { + /* One task get to handle the deleted file chunks. Other tasks can ignore + the chunks of a deleted file */ + if (delete_action) { + auto delete_file_meta = *file_meta; + delete_file_meta.set_deleted_chunk(chunk_num); + err = send_file_metadata(task, &delete_file_meta, false, callback); + } + + auto err2 = close_and_unpin_file(task); + if (err == 0) { + err = err2; + } + + if (err != 0) { + break; /* purecov: inspected */ + } + + /* Send deleted block, to check recipient can handle it. It otherwise is + hit only in rare concurrency case. */ + DBUG_EXECUTE_IF("clone_send_deleted_block", { + if (delete_action && is_page_copy) { + auto data_buf = task->m_current_buffer; + send_data(task, file_ctx, 0, data_buf, UNIV_PAGE_SIZE, 0, callback); + } + }); + + snapshot->skip_deleted_blocks(chunk_num, block_num); + + /* No more blocks in current chunk. */ + if (block_num == 0) { + break; + } + + /* Only in page copy state we can have more blocks belonging to + another tablespace. */ + ut_ad(is_page_copy); + continue; + } + + /* Get next block from snapshot */ + auto data_buf = task->m_current_buffer; + auto data_size = task->m_buffer_alloc_len; + uint64_t data_offset = 0; + uint64_t file_size = 0; + + err = snapshot->get_next_block(chunk_num, block_num, file_ctx, data_offset, + data_buf, data_size, file_size); + + file_meta = file_ctx->get_file_meta_read(); + + /* '0' block number indicates no more blocks. */ + if (err != 0 || block_num == 0) { + break; + } + + /* Check for error from other tasks and DDL */ + err = m_clone_task_manager.handle_error_other_task(task->m_has_thd); + + if (err != 0) { + break; + } + + task->m_task_meta.m_block_num = block_num; + task->m_task_meta.m_chunk_num = chunk_num; + + /* During redo copy, worker could be ahead of master and needs to + send the metadata */ + if (is_redo_copy && !pins_current) { + err = send_file_metadata(task, file_meta, true, callback); + if (err != 0) { + break; + } + } + + /* For remote clone, donor clone threads cannot be controlled + by debug sync and hence need to release PIN after every page so as + to fire concurrent DDLs after blocking clone in apply. In file copy + state we cannot release the PIN as we could be reading the file + while sending data after this point. */ + DBUG_EXECUTE_IF("remote_release_clone_file_pin", { + if (is_page_copy) { + close_and_unpin_file(task); + } + };); + + if (data_size != 0) { + err = send_data(task, file_ctx, data_offset, data_buf, data_size, + file_size, callback); + } + } + + /* Save current error and file name. */ + if (err != 0) { + m_clone_task_manager.set_error(err, file_meta->m_file_name); + } + + return (err); +} + +int Clone_Handle::restart_copy(THD *thd, const byte *loc, uint loc_len) { + mysql_mutex_assert_owner(clone_sys->get_mutex()); + + if (is_abort()) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Restart failed, existing clone aborted"); + return (ER_INTERNAL_ERROR); + } + + /* Wait for the Idle state */ + if (!is_idle()) { + /* Sleep for 1 second */ + Clone_Msec sleep_time(Clone_Sec(1)); + /* Generate alert message every 5 seconds. */ + Clone_Sec alert_time(5); + /* Wait for 30 seconds for server to reach idle state. */ + Clone_Sec time_out(30); + + bool is_timeout = false; + auto err = Clone_Sys::wait( + sleep_time, time_out, alert_time, + [&](bool alert, bool &result) { + mysql_mutex_assert_owner(clone_sys->get_mutex()); + result = !is_idle(); + + if (thd_killed(thd)) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + return (ER_QUERY_INTERRUPTED); + + } else if (is_abort()) { + my_error(ER_INTERNAL_ERROR, MYF(0), + "Innodb Clone Restart failed, existing clone aborted"); + return (ER_INTERNAL_ERROR); + + } else if (Clone_Sys::s_clone_sys_state == CLONE_SYS_ABORT) { + my_error(ER_CLONE_DDL_IN_PROGRESS, MYF(0)); + return (ER_CLONE_DDL_IN_PROGRESS); + } + + if (result && alert) { + ib::info() << "Clone Master Restart " + "wait for idle state"; + } + return (0); + }, + clone_sys->get_mutex(), is_timeout); + + if (err != 0) { + return (err); + + } else if (is_timeout) { + ib::info() + << "Clone Master restart wait for idle timed out"; + + my_error(ER_INTERNAL_ERROR, MYF(0), + "Clone restart wait for idle state timed out"); + return (ER_INTERNAL_ERROR); + } + } + + ut_ad(is_idle()); + m_clone_task_manager.reinit_copy_state(loc, loc_len); + + set_state(CLONE_STATE_ACTIVE); + + return (0); +} + +int Clone_Handle::check_and_pin_file(Clone_Task *task, Clone_file_ctx *file_ctx, + bool &handle_deleted) { + bool pin_current; + bool pin_other; + std::tie(pin_current, pin_other) = pins_file(task, file_ctx); + + handle_deleted = false; + + /* Nothing to do, the file is already pinned. */ + if (pin_current) { + ut_ad(task->m_pinned_file); + return 0; + } + + /* If pinning any other file, release it. */ + if (pin_other) { + /* purecov: begin inspected */ + auto err2 = close_and_unpin_file(task); + if (err2 != 0) { + return err2; + } + /* purecov: end */ + } + + auto snapshot = m_clone_task_manager.get_snapshot(); + auto err = snapshot->pin_file(file_ctx, handle_deleted); + + if (err == 0) { + task->m_pinned_file = true; + const auto file_meta = file_ctx->get_file_meta_read(); + task->m_current_file_index = file_meta->m_file_index; + } + + return err; +} + +int Clone_Handle::close_and_unpin_file(Clone_Task *task) { + /* Close open file if there. */ + auto err = close_file(task); + + /* Task doesn't hold any pin. */ + if (!task->m_pinned_file) { + return err; + } + + auto snapshot = m_clone_task_manager.get_snapshot(); + auto file_ctx = snapshot->get_file_ctx_by_index(task->m_current_file_index); + + if (file_ctx == nullptr) { + /* purecov: begin deadcode */ + err = ER_INTERNAL_ERROR; + my_error(ER_INTERNAL_ERROR, MYF(0), "Clone file missing before unpin"); + ut_d(ut_error); + return err; + /* purecov: end */ + } + + snapshot->unpin_file(file_ctx); + + task->m_pinned_file = false; + task->m_current_file_index = 0; + return err; +} + +std::tuple Clone_Handle::pins_file(const Clone_Task *task, + const Clone_file_ctx *file_ctx) { + /* Task doesn't hold any pin. */ + if (!task->m_pinned_file) { + return std::make_tuple(false, false); + } + + const auto file_meta = file_ctx->get_file_meta_read(); + /* Task pins input file. */ + if (task->m_current_file_index == file_meta->m_file_index) { + return std::make_tuple(true, false); + } + + /* Task pins other file. */ + return std::make_tuple(false, true); +} + +int Clone_Handle::send_all_ddl_metadata(Clone_Task *task, + Ha_clone_cbk *callback) { + ut_ad(m_clone_handle_type == CLONE_HDL_COPY); + + if (!task->m_is_master) { + return 0; + } + auto state = m_clone_task_manager.get_state(); + + /* Send DDL metadata added during 'file copy' and 'page copy' in the + beginning of next stage. */ + if (state != CLONE_SNAPSHOT_PAGE_COPY && state != CLONE_SNAPSHOT_REDO_COPY) { + return 0; + } + + ut_d(m_clone_task_manager.debug_wait_ddl_meta()); + + auto snapshot = m_clone_task_manager.get_snapshot(); + + /* Send all file metadata for data/redo files */ + auto err = snapshot->iterate_data_files([&](Clone_file_ctx *file_ctx) { + /* Skip entries if not modified by ddl in previous state. */ + if (!file_ctx->by_ddl(state)) { + return 0; + } + + auto file_meta = file_ctx->get_file_meta(); + auto err_file = send_file_metadata(task, file_meta, false, callback); + + return err_file; + }); + + return err; +} diff --git a/storage/innobase/clone/clone0desc.cc b/storage/innobase/clone/clone0desc.cc new file mode 100644 index 0000000000000..3dcaf7a3c3299 --- /dev/null +++ b/storage/innobase/clone/clone0desc.cc @@ -0,0 +1,1044 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file clone/clone0desc.cc + Innodb clone descriptors + + *******************************************************/ + +#include "clone0desc.h" +#include "dict0dict.h" +#include "mach0data.h" + +/** Maximum supported descriptor version. The version represents the current +set of descriptors and its elements. */ +static const uint CLONE_DESC_MAX_VERSION = 100; + +/** Header: Version is in first 4 bytes */ +static const uint CLONE_DESC_VER_OFFSET = 0; + +/** Header: Total length is stored in next 4 bytes */ +static const uint CLONE_DESC_LEN_OFFSET = CLONE_DESC_VER_OFFSET + 4; + +/** Header: Descriptor type is in next 4 bytes */ +static const uint CLONE_DESC_TYPE_OFFSET = CLONE_DESC_LEN_OFFSET + 4; + +/** Header: Fixed length. */ +static const uint CLONE_DESC_HEADER_LEN = CLONE_DESC_TYPE_OFFSET + 4; + +uint choose_desc_version(const byte *ref_loc) { + if (ref_loc == nullptr) { + return (CLONE_DESC_MAX_VERSION); + } + + Clone_Desc_Header header; + uint version; + + header.deserialize(ref_loc, CLONE_DESC_HEADER_LEN); + version = header.m_version; + + /* Choose the minimum of remote locator version local + supported version. */ + if (version > CLONE_DESC_MAX_VERSION) { + version = CLONE_DESC_MAX_VERSION; + } + + return (version); +} + +void Clone_Desc_Header::serialize(byte *desc_hdr) { + mach_write_to_4(desc_hdr + CLONE_DESC_VER_OFFSET, m_version); + mach_write_to_4(desc_hdr + CLONE_DESC_LEN_OFFSET, m_length); + mach_write_to_4(desc_hdr + CLONE_DESC_TYPE_OFFSET, m_type); +} + +bool Clone_Desc_Header::deserialize(const byte *desc_hdr, uint desc_len) { + if (desc_len < CLONE_DESC_HEADER_LEN) { + return (false); + } + m_version = mach_read_from_4(desc_hdr + CLONE_DESC_VER_OFFSET); + m_length = mach_read_from_4(desc_hdr + CLONE_DESC_LEN_OFFSET); + + uint int_type; + int_type = mach_read_from_4(desc_hdr + CLONE_DESC_TYPE_OFFSET); + ut_ad(int_type < CLONE_DESC_MAX); + + m_type = static_cast(int_type); + return (true); +} + +/** Task: Clone task index in 4 bytes */ +static const uint CLONE_TASK_INDEX_OFFSET = CLONE_DESC_HEADER_LEN; + +/** Task: Task chunk number in 4 bytes */ +static const uint CLONE_TASK_CHUNK_OFFSET = CLONE_TASK_INDEX_OFFSET + 4; + +/** Task: Task block number in 4 bytes */ +static const uint CLONE_TASK_BLOCK_OFFSET = CLONE_TASK_CHUNK_OFFSET + 4; + +/** Task: Total length */ +static const uint CLONE_TASK_META_LEN = CLONE_TASK_BLOCK_OFFSET + 4; + +/** Initialize header +@param[in] version descriptor version */ +void Clone_Desc_Task_Meta::init_header(uint version) { + m_header.m_version = version; + + m_header.m_length = CLONE_TASK_META_LEN; + + m_header.m_type = CLONE_DESC_TASK_METADATA; +} + +void Clone_Desc_Task_Meta::serialize(byte *&desc_task, uint &len, + mem_heap_t *heap) { + if (desc_task == nullptr) { + len = m_header.m_length; + desc_task = static_cast(mem_heap_alloc(heap, len)); + } else { + ut_ad(len >= m_header.m_length); + len = m_header.m_length; + } + + m_header.serialize(desc_task); + + mach_write_to_4(desc_task + CLONE_TASK_INDEX_OFFSET, + m_task_meta.m_task_index); + mach_write_to_4(desc_task + CLONE_TASK_CHUNK_OFFSET, m_task_meta.m_chunk_num); + mach_write_to_4(desc_task + CLONE_TASK_BLOCK_OFFSET, m_task_meta.m_block_num); +} + +bool Clone_Desc_Task_Meta::deserialize(const byte *desc_task, uint desc_len) { + /* Deserialize the header and validate type and length. */ + if (desc_len < CLONE_TASK_META_LEN || + !m_header.deserialize(desc_task, desc_len) || + m_header.m_type != CLONE_DESC_TASK_METADATA) { + return (false); + } + m_task_meta.m_task_index = + mach_read_from_4(desc_task + CLONE_TASK_INDEX_OFFSET); + m_task_meta.m_chunk_num = + mach_read_from_4(desc_task + CLONE_TASK_CHUNK_OFFSET); + m_task_meta.m_block_num = + mach_read_from_4(desc_task + CLONE_TASK_BLOCK_OFFSET); + return (true); +} + +/** Locator: Clone identifier in 8 bytes */ +static const uint CLONE_LOC_CID_OFFSET = CLONE_DESC_HEADER_LEN; + +/** Locator: Snapshot identifier in 8 bytes */ +static const uint CLONE_LOC_SID_OFFSET = CLONE_LOC_CID_OFFSET + 8; + +/** Locator: Clone array index in 4 bytes */ +static const uint CLONE_LOC_IDX_OFFSET = CLONE_LOC_SID_OFFSET + 8; + +/** Locator: Clone Snapshot state in 1 byte */ +static const uint CLONE_LOC_STATE_OFFSET = CLONE_LOC_IDX_OFFSET + 4; + +/** Locator: Clone Snapshot sub-state in 1 byte */ +static const uint CLONE_LOC_META_OFFSET = CLONE_LOC_STATE_OFFSET + 1; + +/** Locator: Total length */ +static const uint CLONE_DESC_LOC_BASE_LEN = CLONE_LOC_META_OFFSET + 1; + +uint32_t *Chnunk_Bitmap::reset(uint32_t max_bits, mem_heap_t *heap) { + m_bits = max_bits; + + if (max_bits <= capacity()) { + if (m_bitmap != nullptr && size() > 0) { + memset(m_bitmap, 0, size()); + } + return (nullptr); + } + + auto old_buf = m_bitmap; + + m_size = static_cast(m_bits >> 3); + + ut_ad(m_size == m_bits / 8); + + if (max_bits > capacity()) { + ++m_size; + } + + ut_ad(m_bits <= capacity()); + + m_bitmap = static_cast( + mem_heap_zalloc(heap, static_cast(size()))); + + return (old_buf); +} + +uint32_t Chnunk_Bitmap::get_min_unset_bit() { + uint32_t mask = 0; + uint32_t return_bit = 0; + size_t index = 0; + + mask = ~mask; + + /* Find the first block with unset BIT */ + for (index = 0; index < m_size; ++index) { + if ((m_bitmap[index] & mask) != mask || return_bit >= m_bits) { + break; + } + + return_bit += 32; + } + + /* All BITs are set */ + if (index >= m_size || return_bit >= m_bits) { + return (m_bits + 1); + } + + auto val = m_bitmap[index]; + ut_ad((val & mask) != mask); + + index = 0; + + /* Find the unset BIT within block */ + do { + mask = 1 << index; + + if ((val & mask) == 0) { + break; + } + + } while (++index < 32); + + ut_ad(index < 32); + + return_bit += static_cast(index); + + /* Change from 0 to 1 based index */ + ++return_bit; + + ut_ad(return_bit <= m_bits + 1); + + return (return_bit); +} + +uint32_t Chnunk_Bitmap::get_max_set_bit() { + uint32_t return_bit = 0; + size_t block_index = 0; + size_t index = 0; + + /* Find the last block with set BIT */ + for (index = 0; index < m_size; ++index) { + if (return_bit >= m_bits) { + break; + } + + if (m_bitmap[index] != 0) { + block_index = index + 1; + } + + return_bit += 32; + } + + /* No BITs are set */ + if (block_index == 0) { + return (0); + } + + --block_index; + return_bit = static_cast(block_index * 32); + + auto val = m_bitmap[block_index]; + ut_ad(val != 0); + + uint32_t mask = 0; + index = 0; + + /* Find the last BIT set within block */ + do { + mask = 1 << index; + + if ((val & mask) != 0) { + block_index = index; + } + + } while (++index < 32); + + return_bit += static_cast(block_index); + + /* Change from 0 to 1 based index */ + ++return_bit; + + ut_ad(return_bit <= m_bits); + + return (return_bit); +} + +size_t Chnunk_Bitmap::get_serialized_length() { + /* Length of chunk BITMAP data */ + size_t ret_size = 4; + + /* Add size for chunk bitmap data */ + ret_size += size(); + + return (ret_size); +} + +size_t Chunk_Info::get_serialized_length(uint32_t num_tasks) { + /* Length of incomplete chunk data */ + size_t ret_size = 4; + + auto num_elements = m_incomplete_chunks.size(); + auto suggested_elements = 2 * num_tasks; + + /* Have bigger allocated length if requested */ + if (suggested_elements > num_elements) { + num_elements = suggested_elements; + } + + /* Add size for incomplete chunks data. Serialized element + has chunk and block number: 4 + 4 = 8 bytes */ + ret_size += (8 * num_elements); + + /* Add length of chunk bitmap data */ + ret_size += m_reserved_chunks.get_serialized_length(); + + return (ret_size); +} + +void Chnunk_Bitmap::serialize(byte *&desc_chunk, uint &len) { + auto len_left = len; + auto bitmap_size = static_cast(m_size); + + mach_write_to_4(desc_chunk, bitmap_size); + desc_chunk += 4; + + ut_ad(len_left >= 4); + len_left -= 4; + + for (size_t index = 0; index < m_size; ++index) { + auto val = static_cast(m_bitmap[index]); + + mach_write_to_4(desc_chunk, val); + desc_chunk += 4; + + ut_ad(len_left >= 4); + len_left -= 4; + } + + ut_ad(len > len_left); + len -= len_left; +} + +void Chunk_Info::serialize(byte *desc_chunk, uint &len) { + auto len_left = len; + auto chunk_map_size = static_cast(m_incomplete_chunks.size()); + + mach_write_to_4(desc_chunk, chunk_map_size); + desc_chunk += 4; + + ut_ad(len_left >= 4); + len_left -= 4; + + ulint index [[maybe_unused]] = 0; + + for (auto &key_value : m_incomplete_chunks) { + ut_ad(index < chunk_map_size); + + mach_write_to_4(desc_chunk, key_value.first); + desc_chunk += 4; + + ut_ad(len_left >= 4); + len_left -= 4; + + mach_write_to_4(desc_chunk, key_value.second); + desc_chunk += 4; + + ut_ad(len_left >= 4); + len_left -= 4; + + ++index; + } + ut_ad(index == chunk_map_size); + + /* Actual length for serialized chunk map */ + ut_ad(len > len_left); + len -= len_left; + + m_reserved_chunks.serialize(desc_chunk, len_left); + + /* Total serialized length */ + len += len_left; +} + +void Chnunk_Bitmap::deserialize(const byte *desc_chunk, uint &len_left) { + auto bitmap_size = mach_read_from_4(desc_chunk); + desc_chunk += 4; + + if (len_left < 4) { + ut_d(ut_error); + return; + } + + len_left -= 4; + + if (bitmap_size > m_size) { + ut_d(ut_error); + return; + } + + for (ulint index = 0; index < bitmap_size; index++) { + m_bitmap[index] = static_cast(mach_read_from_4(desc_chunk)); + + desc_chunk += 4; + + if (len_left < 4) { + ut_d(ut_error); + return; + } + + len_left -= 4; + } + + ut_ad(len_left == 0); +} + +void Chunk_Info::deserialize(const byte *desc_chunk, uint &len_left) { + auto chunk_map_size = mach_read_from_4(desc_chunk); + + desc_chunk += 4; + + if (len_left < 4) { + ut_d(ut_error); + return; + } + + len_left -= 4; + + auto max_map_size = static_cast(2 * CLONE_MAX_TASKS); + /* Each task can have one incomplete chunk at most */ + if (chunk_map_size > max_map_size) { + ib::error() + << "Clone too many incomplete chunks: " << chunk_map_size; + ut_d(ut_error); + return; + } + + for (ulint index = 0; index < chunk_map_size; index++) { + auto chunk_num = static_cast(mach_read_from_4(desc_chunk)); + + desc_chunk += 4; + + if (len_left < 4) { + ut_d(ut_error); + return; + } + len_left -= 4; + + auto block_num = static_cast(mach_read_from_4(desc_chunk)); + desc_chunk += 4; + + if (len_left < 4) { + ut_d(ut_error); + return; + } + len_left -= 4; + + m_incomplete_chunks[chunk_num] = block_num; + } + + m_reserved_chunks.deserialize(desc_chunk, len_left); + + ut_ad(len_left == 0); +} + +void Clone_Desc_Locator::init(uint64_t id, uint64_t snap_id, + Snapshot_State state, uint version, uint index) { + m_header.m_version = version; + + m_header.m_length = CLONE_DESC_LOC_BASE_LEN; + + m_header.m_type = CLONE_DESC_LOCATOR; + + m_clone_id = id; + m_snapshot_id = snap_id; + + m_clone_index = index; + m_state = state; + m_metadata_transferred = false; +} + +bool Clone_Desc_Locator::match(Clone_Desc_Locator *other_desc) { +#ifdef UNIV_DEBUG + Clone_Desc_Header *other_header = &other_desc->m_header; +#endif /* UNIV_DEBUG */ + + if (other_desc->m_clone_id == m_clone_id && + other_desc->m_snapshot_id == m_snapshot_id) { + ut_ad(m_header.m_version == other_header->m_version); + return (true); + } + + return (false); +} + +void Clone_Desc_Locator::serialize(byte *&desc_loc, uint &len, + Chunk_Info *chunk_info, mem_heap_t *heap) { + if (chunk_info != nullptr) { + auto chunk_len = static_cast(chunk_info->get_serialized_length(0)); + + m_header.m_length += chunk_len; + } + + if (desc_loc == nullptr) { + len = m_header.m_length; + desc_loc = static_cast(mem_heap_alloc(heap, len)); + } else { + ut_ad(len >= m_header.m_length); + len = m_header.m_length; + } + + m_header.serialize(desc_loc); + + mach_write_to_8(desc_loc + CLONE_LOC_CID_OFFSET, m_clone_id); + mach_write_to_8(desc_loc + CLONE_LOC_SID_OFFSET, m_snapshot_id); + + mach_write_to_4(desc_loc + CLONE_LOC_IDX_OFFSET, m_clone_index); + + mach_write_to_1(desc_loc + CLONE_LOC_STATE_OFFSET, + static_cast(m_state)); + + ulint sub_state = m_metadata_transferred ? 1 : 0; + + mach_write_to_1(desc_loc + CLONE_LOC_META_OFFSET, sub_state); + + if (chunk_info != nullptr) { + ut_ad(len > CLONE_DESC_LOC_BASE_LEN); + + auto len_left = len - CLONE_DESC_LOC_BASE_LEN; + + chunk_info->serialize(desc_loc + CLONE_DESC_LOC_BASE_LEN, len_left); + } +} + +bool clone_validate_locator(const byte *desc_loc, uint desc_len) { + Clone_Desc_Header header; + + if (!header.deserialize(desc_loc, desc_len)) { + ut_d(ut_error); + return false; + } + if (desc_len < CLONE_DESC_LOC_BASE_LEN || + header.m_length < CLONE_DESC_LOC_BASE_LEN || header.m_length > desc_len || + header.m_type != CLONE_DESC_LOCATOR) { + ut_d(ut_error); + return false; + } + return true; +} + +void Clone_Desc_Locator::deserialize(const byte *desc_loc, uint desc_len, + Chunk_Info *chunk_info) { + m_header.deserialize(desc_loc, CLONE_DESC_HEADER_LEN); + + ut_ad(m_header.m_type == CLONE_DESC_LOCATOR); + + if (m_header.m_length < CLONE_DESC_LOC_BASE_LEN || + m_header.m_length > desc_len) { + ut_d(ut_error); + return; + } + + m_clone_id = mach_read_from_8(desc_loc + CLONE_LOC_CID_OFFSET); + m_snapshot_id = mach_read_from_8(desc_loc + CLONE_LOC_SID_OFFSET); + + m_clone_index = mach_read_from_4(desc_loc + CLONE_LOC_IDX_OFFSET); + + m_state = static_cast( + mach_read_from_1(desc_loc + CLONE_LOC_STATE_OFFSET)); + + auto sub_state = mach_read_from_1(desc_loc + CLONE_LOC_META_OFFSET); + m_metadata_transferred = (sub_state == 0) ? false : true; + + ut_ad(m_header.m_length >= CLONE_DESC_LOC_BASE_LEN); + + auto len_left = m_header.m_length - CLONE_DESC_LOC_BASE_LEN; + + if (chunk_info != nullptr && len_left != 0) { + chunk_info->deserialize(desc_loc + CLONE_DESC_LOC_BASE_LEN, len_left); + } +} + +/** Check a specific bit in flag. +@param[in] flag bit flag +@param[in] bit check bit +@return true, iff bit is set in flag. */ +inline bool DESC_CHECK_FLAG(ulint flag, ulint bit) { + return ((flag & (1ULL << (bit - 1))) > 0); +} + +/** Set a specific bit in flag. +@param[in] flag bit flag +@param[in] bit set bit */ +inline void DESC_SET_FLAG(ulint &flag, ulint bit) { + flag |= static_cast(1ULL << (bit - 1)); +} + +/** File Metadata: Snapshot state in 4 bytes */ +static const uint CLONE_FILE_STATE_OFFSET = CLONE_DESC_HEADER_LEN; + +/** File Metadata: File size in 8 bytes */ +static const uint CLONE_FILE_SIZE_OFFSET = CLONE_FILE_STATE_OFFSET + 4; + +/** File Metadata: Sparse file allocation size on disk in 8 bytes */ +static const uint CLONE_FILE_ALLOC_SIZE_OFFSET = CLONE_FILE_SIZE_OFFSET + 8; + +/** File Metadata: FSP flags in 4 bytes */ +static const uint CLONE_FILE_FSP_OFFSET = CLONE_FILE_ALLOC_SIZE_OFFSET + 8; + +/** File Metadata: File system block size for compressed tables in 4 bytes. */ +static const uint CLONE_FILE_FSBLK_OFFSET = CLONE_FILE_FSP_OFFSET + 4; + +/** File Metadata: File space flags in next 2 bytes [Maximum 16 flags] */ +static const uint CLONE_FILE_FLAGS_OFFSET = CLONE_FILE_FSBLK_OFFSET + 4; +/** Clone File Flag: File is renamed. */ +static const uint CLONE_DESC_FILE_FLAG_RENAMED = 2; +/** Clone File Flag: File is deleted. */ +static const uint CLONE_DESC_FILE_FLAG_DELETED = 3; +/** Clone File Flag: File metadata has encryption key. */ +static const uint CLONE_DESC_FILE_HAS_KEY = 4; + +/** File Metadata: Tablespace ID in 4 bytes */ +static const uint CLONE_FILE_SPACE_ID_OFFSET = CLONE_FILE_FLAGS_OFFSET + 2; + +/** File Metadata: File index in 4 bytes */ +static const uint CLONE_FILE_IDX_OFFSET = CLONE_FILE_SPACE_ID_OFFSET + 4; + +/** File Metadata: First chunk number in 4 bytes */ +static const uint CLONE_FILE_BCHUNK_OFFSET = CLONE_FILE_IDX_OFFSET + 4; + +/** File Metadata: Last chunk number in 4 bytes */ +static const uint CLONE_FILE_ECHUNK_OFFSET = CLONE_FILE_BCHUNK_OFFSET + 4; + +/** File Metadata: File name length in 4 bytes */ +static const uint CLONE_FILE_FNAMEL_OFFSET = CLONE_FILE_ECHUNK_OFFSET + 4; + +/** File Metadata: File name */ +static const uint CLONE_FILE_FNAME_OFFSET = CLONE_FILE_FNAMEL_OFFSET + 4; + +/** File Metadata: Length excluding the file name */ +static const uint CLONE_FILE_BASE_LEN = CLONE_FILE_FNAME_OFFSET; + +void Clone_Desc_File_MetaData::init_header(uint version) { + m_header.m_version = version; + + m_header.m_length = CLONE_FILE_BASE_LEN; + m_header.m_length += static_cast(m_file_meta.m_file_name_len); + + /* TODO: Encryption metadata transfer */ + ut_ad(!m_file_meta.m_transfer_encryption_key); + m_header.m_type = CLONE_DESC_FILE_METADATA; +} + +void Clone_Desc_File_MetaData::serialize(byte *&desc_file, uint &len, + mem_heap_t *heap) { + /* Allocate descriptor if needed. */ + if (desc_file == nullptr) { + len = m_header.m_length; + ut_ad(len == CLONE_FILE_FNAME_OFFSET + m_file_meta.m_file_name_len); + + desc_file = static_cast(mem_heap_alloc(heap, len)); + } else { + ut_ad(len >= m_header.m_length); + len = m_header.m_length; + } + + m_header.serialize(desc_file); + + mach_write_to_4(desc_file + CLONE_FILE_STATE_OFFSET, m_state); + + mach_write_to_8(desc_file + CLONE_FILE_SIZE_OFFSET, m_file_meta.m_file_size); + mach_write_to_8(desc_file + CLONE_FILE_ALLOC_SIZE_OFFSET, + m_file_meta.m_alloc_size); + mach_write_to_4(desc_file + CLONE_FILE_FSP_OFFSET, m_file_meta.m_fsp_flags); + + mach_write_to_4(desc_file + CLONE_FILE_FSBLK_OFFSET, + m_file_meta.m_fsblk_size); + /* Set file compression type for sparse file. */ + ulint file_flags = 0; + /* TODO: Encryption metadata transfer: Set file encryption type */ + ut_ad(!m_file_meta.m_transfer_encryption_key); + + /* Set file renamed attribute */ + if (m_file_meta.m_renamed) { + DESC_SET_FLAG(file_flags, CLONE_DESC_FILE_FLAG_RENAMED); + } + /* Set file deleted attribute */ + if (m_file_meta.m_deleted) { + DESC_SET_FLAG(file_flags, CLONE_DESC_FILE_FLAG_DELETED); + } + + if (m_file_meta.m_transfer_encryption_key) { + DESC_SET_FLAG(file_flags, CLONE_DESC_FILE_HAS_KEY); + } + + mach_write_to_2(desc_file + CLONE_FILE_FLAGS_OFFSET, file_flags); + + mach_write_to_4(desc_file + CLONE_FILE_SPACE_ID_OFFSET, + m_file_meta.m_space_id); + mach_write_to_4(desc_file + CLONE_FILE_IDX_OFFSET, m_file_meta.m_file_index); + + mach_write_to_4(desc_file + CLONE_FILE_BCHUNK_OFFSET, + m_file_meta.m_begin_chunk); + mach_write_to_4(desc_file + CLONE_FILE_ECHUNK_OFFSET, + m_file_meta.m_end_chunk); + + mach_write_to_4(desc_file + CLONE_FILE_FNAMEL_OFFSET, + m_file_meta.m_file_name_len); + + /* Copy variable length file name. */ + if (m_file_meta.m_file_name_len != 0) { + memcpy(static_cast(desc_file + CLONE_FILE_FNAME_OFFSET), + static_cast(m_file_meta.m_file_name), + m_file_meta.m_file_name_len); + } + + // auto dest_key = + // desc_file + CLONE_FILE_FNAME_OFFSET + m_file_meta.m_file_name_len; + + /* Append Encryption key information if requested. */ + /* TODO: Encryption metadata transfer: Set file encryption type */ + ut_ad(!m_file_meta.m_transfer_encryption_key); +} + +bool Clone_Desc_File_MetaData::deserialize(const byte *desc_file, + uint desc_len) { + /* Deserialize the header and validate type and length. */ + if (desc_len < CLONE_FILE_BASE_LEN || + !m_header.deserialize(desc_file, desc_len) || + m_header.m_type != CLONE_DESC_FILE_METADATA) { + return (false); + } + desc_len -= CLONE_FILE_BASE_LEN; + + auto int_type = mach_read_from_4(desc_file + CLONE_FILE_STATE_OFFSET); + + m_state = static_cast(int_type); + + m_file_meta.m_file_size = + mach_read_from_8(desc_file + CLONE_FILE_SIZE_OFFSET); + m_file_meta.m_alloc_size = + mach_read_from_8(desc_file + CLONE_FILE_ALLOC_SIZE_OFFSET); + + m_file_meta.m_fsp_flags = mach_read_from_4(desc_file + CLONE_FILE_FSP_OFFSET); + m_file_meta.m_fsblk_size = + mach_read_from_4(desc_file + CLONE_FILE_FSBLK_OFFSET); + + m_file_meta.m_punch_hole= false; + m_file_meta.m_is_compressed= m_file_meta.m_fsp_flags != ULINT32_UNDEFINED + && fil_space_t::is_compressed(m_file_meta.m_fsp_flags); + + auto file_flags = + static_cast(mach_read_from_2(desc_file + CLONE_FILE_FLAGS_OFFSET)); + + /* Get file encryption information */ + /* TODO: Encryption metadata transfer: Set file encryption type */ + + /* Get file renamed attribute */ + m_file_meta.m_renamed = + DESC_CHECK_FLAG(file_flags, CLONE_DESC_FILE_FLAG_RENAMED); + + /* Get file renamed attribute */ + m_file_meta.m_deleted = + DESC_CHECK_FLAG(file_flags, CLONE_DESC_FILE_FLAG_DELETED); + + m_file_meta.m_space_id = + mach_read_from_4(desc_file + CLONE_FILE_SPACE_ID_OFFSET); + m_file_meta.m_file_index = + mach_read_from_4(desc_file + CLONE_FILE_IDX_OFFSET); + + m_file_meta.m_begin_chunk = + mach_read_from_4(desc_file + CLONE_FILE_BCHUNK_OFFSET); + m_file_meta.m_end_chunk = + mach_read_from_4(desc_file + CLONE_FILE_ECHUNK_OFFSET); + + m_file_meta.m_file_name_len = + mach_read_from_4(desc_file + CLONE_FILE_FNAMEL_OFFSET); + + m_file_meta.m_file_name_alloc_len = 0; + + /* Check if we have enough length. */ + if (desc_len < m_file_meta.m_file_name_len) { + return (false); + } + + desc_len -= static_cast(m_file_meta.m_file_name_len); + + if (m_file_meta.m_file_name_len == 0) { + m_file_meta.m_file_name = nullptr; + } else { + m_file_meta.m_file_name = + reinterpret_cast(desc_file + CLONE_FILE_FNAME_OFFSET); + auto last_char = m_file_meta.m_file_name[m_file_meta.m_file_name_len - 1]; + + /* File name must be NULL terminated. */ + if (last_char != '\0') { + return (false); + } + } + + /* Check if encryption key is transferred. */ + m_file_meta.m_transfer_encryption_key = + DESC_CHECK_FLAG(file_flags, CLONE_DESC_FILE_HAS_KEY); + + /* Extract Encryption key information if transferred. */ + /* TODO: Encryption metadata transfer: Set file encryption type */ + ut_ad(!m_file_meta.m_transfer_encryption_key); + m_file_meta.m_is_encrypted= false; + ut_ad(m_header.m_length + == CLONE_FILE_FNAME_OFFSET + m_file_meta.m_file_name_len); + return (true); +} + +/** Clone State: Snapshot state in 4 bytes */ +static const uint CLONE_DESC_STATE_OFFSET = CLONE_DESC_HEADER_LEN; + +/** Clone State: Task index in 4 bytes */ +static const uint CLONE_DESC_TASK_OFFSET = CLONE_DESC_STATE_OFFSET + 4; + +/** Clone State: Number of chunks in 4 bytes */ +static const uint CLONE_DESC_STATE_NUM_CHUNKS = CLONE_DESC_TASK_OFFSET + 4; + +/** Clone State: Number of files in 4 bytes */ +static const uint CLONE_DESC_STATE_NUM_FILES = CLONE_DESC_STATE_NUM_CHUNKS + 4; + +/** Clone State: Estimated number of bytes in 8 bytes */ +static const uint CLONE_DESC_STATE_EST_BYTES = CLONE_DESC_STATE_NUM_FILES + 4; + +/** Clone State: Estimated number of bytes in 8 bytes */ +static const uint CLONE_DESC_STATE_EST_DISK = CLONE_DESC_STATE_EST_BYTES + 8; + +/** Clone State: flags in 2 byte [max 16 flags] */ +static const uint CLONE_DESC_STATE_FLAGS = CLONE_DESC_STATE_EST_DISK + 8; + +/** Clone State: Total length */ +static const uint CLONE_DESC_STATE_LEN = CLONE_DESC_STATE_FLAGS + 2; + +/** Clone State Flag: Start processing state */ +static const uint CLONE_DESC_STATE_FLAG_START = 1; + +/** Clone State Flag: Acknowledge processing state */ +static const uint CLONE_DESC_STATE_FLAG_ACK = 2; + +void Clone_Desc_State::init_header(uint version) { + m_header.m_version = version; + + m_header.m_length = CLONE_DESC_STATE_LEN; + + m_header.m_type = CLONE_DESC_STATE; +} + +void Clone_Desc_State::serialize(byte *&desc_state, uint &len, + mem_heap_t *heap) { + /* Allocate descriptor if needed. */ + if (desc_state == nullptr) { + len = m_header.m_length; + desc_state = static_cast(mem_heap_alloc(heap, len)); + } else { + ut_ad(len >= m_header.m_length); + len = m_header.m_length; + } + + m_header.serialize(desc_state); + + mach_write_to_4(desc_state + CLONE_DESC_STATE_OFFSET, m_state); + mach_write_to_4(desc_state + CLONE_DESC_TASK_OFFSET, m_task_index); + + mach_write_to_4(desc_state + CLONE_DESC_STATE_NUM_CHUNKS, m_num_chunks); + mach_write_to_4(desc_state + CLONE_DESC_STATE_NUM_FILES, m_num_files); + mach_write_to_8(desc_state + CLONE_DESC_STATE_EST_BYTES, m_estimate); + mach_write_to_8(desc_state + CLONE_DESC_STATE_EST_DISK, m_estimate_disk); + + ulint state_flags = 0; + + if (m_is_start) { + DESC_SET_FLAG(state_flags, CLONE_DESC_STATE_FLAG_START); + } + + if (m_is_ack) { + DESC_SET_FLAG(state_flags, CLONE_DESC_STATE_FLAG_ACK); + } + + mach_write_to_2(desc_state + CLONE_DESC_STATE_FLAGS, state_flags); +} + +bool Clone_Desc_State::deserialize(const byte *desc_state, uint desc_len) { + /* Deserialize the header and validate type and length. */ + if (desc_len < CLONE_DESC_STATE_LEN || + !m_header.deserialize(desc_state, desc_len) || + m_header.m_type != CLONE_DESC_STATE) { + return (false); + } + + uint int_type; + int_type = mach_read_from_4(desc_state + CLONE_DESC_STATE_OFFSET); + + m_state = static_cast(int_type); + + m_task_index = mach_read_from_4(desc_state + CLONE_DESC_TASK_OFFSET); + + m_num_chunks = mach_read_from_4(desc_state + CLONE_DESC_STATE_NUM_CHUNKS); + m_num_files = mach_read_from_4(desc_state + CLONE_DESC_STATE_NUM_FILES); + m_estimate = mach_read_from_8(desc_state + CLONE_DESC_STATE_EST_BYTES); + m_estimate_disk = mach_read_from_8(desc_state + CLONE_DESC_STATE_EST_DISK); + + auto state_flags = + static_cast(mach_read_from_2(desc_state + CLONE_DESC_STATE_FLAGS)); + + m_is_start = DESC_CHECK_FLAG(state_flags, CLONE_DESC_STATE_FLAG_START); + + m_is_ack = DESC_CHECK_FLAG(state_flags, CLONE_DESC_STATE_FLAG_ACK); + + return (true); +} + +/** Clone Data: Snapshot state in 4 bytes */ +static const uint CLONE_DATA_STATE_OFFSET = CLONE_DESC_HEADER_LEN; + +/** Clone Data: Task index in 4 bytes */ +static const uint CLONE_DATA_TASK_INDEX_OFFSET = CLONE_DATA_STATE_OFFSET + 4; + +/** Clone Data: Current chunk number in 4 bytes */ +static const uint CLONE_DATA_TASK_CHUNK_OFFSET = + CLONE_DATA_TASK_INDEX_OFFSET + 4; + +/** Clone Data: Current block number in 4 bytes */ +static const uint CLONE_DATA_TASK_BLOCK_OFFSET = + CLONE_DATA_TASK_CHUNK_OFFSET + 4; + +/** Clone Data: Data file index in 4 bytes */ +static const uint CLONE_DATA_FILE_IDX_OFFSET = CLONE_DATA_TASK_BLOCK_OFFSET + 4; + +/** Clone Data: Data length in 4 bytes */ +static const uint CLONE_DATA_LEN_OFFSET = CLONE_DATA_FILE_IDX_OFFSET + 4; + +/** Clone Data: Data file offset in 8 bytes */ +static const uint CLONE_DATA_FOFF_OFFSET = CLONE_DATA_LEN_OFFSET + 4; + +/** Clone Data: Updated file size in 8 bytes */ +static const uint CLONE_DATA_FILE_SIZE_OFFSET = CLONE_DATA_FOFF_OFFSET + 8; + +/** Clone Data: Total length */ +static const uint CLONE_DESC_DATA_LEN = CLONE_DATA_FILE_SIZE_OFFSET + 8; + +void Clone_Desc_Data::init_header(uint version) { + m_header.m_version = version; + + m_header.m_length = CLONE_DESC_DATA_LEN; + + m_header.m_type = CLONE_DESC_DATA; +} + +void Clone_Desc_Data::serialize(byte *&desc_data, uint &len, mem_heap_t *heap) { + /* Allocate descriptor if needed. */ + if (desc_data == nullptr) { + len = m_header.m_length; + desc_data = static_cast(mem_heap_alloc(heap, len)); + } else { + ut_ad(len >= m_header.m_length); + len = m_header.m_length; + } + + m_header.serialize(desc_data); + + mach_write_to_4(desc_data + CLONE_DATA_STATE_OFFSET, m_state); + + mach_write_to_4(desc_data + CLONE_DATA_TASK_INDEX_OFFSET, + m_task_meta.m_task_index); + mach_write_to_4(desc_data + CLONE_DATA_TASK_CHUNK_OFFSET, + m_task_meta.m_chunk_num); + mach_write_to_4(desc_data + CLONE_DATA_TASK_BLOCK_OFFSET, + m_task_meta.m_block_num); + + mach_write_to_4(desc_data + CLONE_DATA_FILE_IDX_OFFSET, m_file_index); + mach_write_to_4(desc_data + CLONE_DATA_LEN_OFFSET, m_data_len); + mach_write_to_8(desc_data + CLONE_DATA_FOFF_OFFSET, m_file_offset); + mach_write_to_8(desc_data + CLONE_DATA_FILE_SIZE_OFFSET, m_file_size); +} + +bool Clone_Desc_Data::deserialize(const byte *desc_data, uint desc_len) { + /* Deserialize the header and validate type and length. */ + if (desc_len < CLONE_DESC_DATA_LEN || + !m_header.deserialize(desc_data, desc_len) || + m_header.m_type != CLONE_DESC_DATA) { + return (false); + } + + uint int_type; + int_type = mach_read_from_4(desc_data + CLONE_DATA_STATE_OFFSET); + + m_state = static_cast(int_type); + + m_task_meta.m_task_index = + mach_read_from_4(desc_data + CLONE_DATA_TASK_INDEX_OFFSET); + + m_task_meta.m_chunk_num = + mach_read_from_4(desc_data + CLONE_DATA_TASK_CHUNK_OFFSET); + + m_task_meta.m_block_num = + mach_read_from_4(desc_data + CLONE_DATA_TASK_BLOCK_OFFSET); + + m_file_index = mach_read_from_4(desc_data + CLONE_DATA_FILE_IDX_OFFSET); + m_data_len = mach_read_from_4(desc_data + CLONE_DATA_LEN_OFFSET); + m_file_offset = mach_read_from_8(desc_data + CLONE_DATA_FOFF_OFFSET); + m_file_size = mach_read_from_8(desc_data + CLONE_DATA_FILE_SIZE_OFFSET); + + return (true); +} + +void Clone_File_Meta::init() { + m_file_index = 0; + m_file_name = nullptr; + m_file_name_len = 0; + m_file_name_alloc_len = 0; + + m_file_size = 0; + m_alloc_size = 0; + + m_space_id = UINT32_MAX; + m_fsp_flags = ULINT32_UNDEFINED; + + /* TODO: Encryption metadata transfer: Set file encryption type */ + // m_encryption_metadata = {}; + m_punch_hole = false; + m_fsblk_size = 0; + + m_is_compressed= false; + m_is_encrypted= false; + + m_transfer_encryption_key = false; + + m_begin_chunk = 0; + m_end_chunk = 0; + + reset_ddl(); +} diff --git a/storage/innobase/clone/clone0snapshot.cc b/storage/innobase/clone/clone0snapshot.cc new file mode 100644 index 0000000000000..ae420f5c1155f --- /dev/null +++ b/storage/innobase/clone/clone0snapshot.cc @@ -0,0 +1,1551 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file clone/clone0snapshot.cc + Innodb physical Snapshot + + *******************************************************/ + +#include "buf0flu.h" +#include "clone0snapshot.h" +#include "clone0clone.h" +#include "fil0pagecompress.h" +#include "log0log.h" /* log_get_lsn */ +#include "page0zip.h" +#include "handler.h" + +/** Snapshot heap initial size */ +const uint SNAPSHOT_MEM_INITIAL_SIZE = 16 * 1024; + +/** Number of clones that can attach to a snapshot. */ +const uint MAX_CLONES_PER_SNAPSHOT = 1; + +Clone_Snapshot::Clone_Snapshot(Clone_Handle_Type hdl_type, + Ha_clone_type clone_type, uint arr_idx, + uint64_t snap_id) + : m_snapshot_handle_type(hdl_type), + m_snapshot_type(clone_type), + m_snapshot_id(snap_id), + m_snapshot_arr_idx(arr_idx), + m_num_blockers(), + m_aborted(false), + m_num_clones(), + m_num_clones_transit(), + m_snapshot_state(CLONE_SNAPSHOT_INIT), + m_snapshot_next_state(CLONE_SNAPSHOT_NONE), + m_num_current_chunks(), + m_max_file_name_len(), + m_num_data_chunks(), + m_data_bytes_disk(), + m_page_ctx(false), + m_num_pages(), + m_num_duplicate_pages(), + m_redo_ctx(), + m_redo_start_offset(), + m_redo_header(), + m_redo_header_size(), + m_redo_trailer(), + m_redo_trailer_size(), + m_redo_trailer_offset(), + m_redo_file_size(), + m_num_redo_chunks(), + m_enable_pfs(false) { + mysql_mutex_init(0, &m_snapshot_mutex, nullptr); + + m_snapshot_heap= mem_heap_create(SNAPSHOT_MEM_INITIAL_SIZE); + + m_chunk_size_pow2 = SNAPSHOT_DEF_CHUNK_SIZE_POW2; + m_block_size_pow2 = SNAPSHOT_DEF_BLOCK_SIZE_POW2; +} + +Clone_Snapshot::~Clone_Snapshot() { + m_redo_ctx.release(); + + if (m_page_ctx.is_active()) { + m_page_ctx.stop(nullptr); + } + m_page_ctx.release(); + + mem_heap_free(m_snapshot_heap); + + mysql_mutex_destroy(&m_snapshot_mutex); +} + +void Clone_Snapshot::get_state_info(bool do_estimate, + Clone_Desc_State *state_desc) { + state_desc->m_state = m_snapshot_state; + state_desc->m_num_chunks = m_num_current_chunks; + + state_desc->m_is_start = true; + state_desc->m_is_ack = false; + + if (do_estimate) { + state_desc->m_estimate = m_monitor.get_estimate(); + state_desc->m_estimate_disk = m_data_bytes_disk; + } else { + state_desc->m_estimate = 0; + state_desc->m_estimate_disk = 0; + } + + switch (m_snapshot_state) { + case CLONE_SNAPSHOT_FILE_COPY: + state_desc->m_num_files = static_cast(num_data_files()); + break; + + case CLONE_SNAPSHOT_PAGE_COPY: + state_desc->m_num_files = m_num_pages; + break; + + case CLONE_SNAPSHOT_REDO_COPY: + state_desc->m_num_files = static_cast(num_redo_files()); + break; + + case CLONE_SNAPSHOT_DONE: + case CLONE_SNAPSHOT_INIT: + state_desc->m_num_files = 0; + break; + + default: + state_desc->m_num_files = 0; + ut_d(ut_error); + } +} + +void Clone_Snapshot::set_state_info(Clone_Desc_State *state_desc) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + + m_snapshot_state = state_desc->m_state; + m_num_current_chunks = state_desc->m_num_chunks; + + if (m_snapshot_state == CLONE_SNAPSHOT_FILE_COPY) { + m_num_data_chunks = state_desc->m_num_chunks; + m_data_bytes_disk = state_desc->m_estimate_disk; + m_data_file_vector.resize(state_desc->m_num_files, nullptr); + + m_monitor.init_state(srv_stage_clone_file_copy.m_key, m_enable_pfs); + m_monitor.add_estimate(state_desc->m_estimate); + m_monitor.change_phase(); + + } else if (m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY) { + m_num_pages = state_desc->m_num_files; + + m_monitor.init_state(srv_stage_clone_page_copy.m_key, m_enable_pfs); + m_monitor.add_estimate(state_desc->m_estimate); + m_monitor.change_phase(); + + } else if (m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY) { + m_num_redo_chunks = state_desc->m_num_chunks; + m_redo_file_vector.resize(state_desc->m_num_files, nullptr); + + m_monitor.init_state(srv_stage_clone_redo_copy.m_key, m_enable_pfs); + m_monitor.add_estimate(state_desc->m_estimate); + m_monitor.change_phase(); + + } else if (m_snapshot_state == CLONE_SNAPSHOT_DONE) { + ut_ad(m_num_current_chunks == 0); + m_monitor.init_state(PSI_NOT_INSTRUMENTED, m_enable_pfs); + + } else { + ut_d(ut_error); + } +} + +Snapshot_State Clone_Snapshot::get_next_state() { + Snapshot_State next_state; + + ut_ad(m_snapshot_state != CLONE_SNAPSHOT_NONE); + + if (m_snapshot_state == CLONE_SNAPSHOT_INIT) { + next_state = CLONE_SNAPSHOT_FILE_COPY; + + } else if (m_snapshot_state == CLONE_SNAPSHOT_FILE_COPY) { + if (m_snapshot_type == HA_CLONE_HYBRID || + m_snapshot_type == HA_CLONE_PAGE) { + next_state = CLONE_SNAPSHOT_PAGE_COPY; + + } else if (m_snapshot_type == HA_CLONE_REDO) { + next_state = CLONE_SNAPSHOT_REDO_COPY; + + } else { + ut_ad(m_snapshot_type == HA_CLONE_BLOCKING); + next_state = CLONE_SNAPSHOT_DONE; + } + + } else if (m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY) { + next_state = CLONE_SNAPSHOT_REDO_COPY; + + } else { + ut_ad(m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY); + next_state = CLONE_SNAPSHOT_DONE; + } + + return (next_state); +} + +bool Clone_Snapshot::attach(Clone_Handle_Type hdl_type, bool pfs_monitor) { + bool ret = false; + mysql_mutex_lock(&m_snapshot_mutex); + + if (hdl_type == m_snapshot_handle_type && + m_num_clones < MAX_CLONES_PER_SNAPSHOT) { + ++m_num_clones; + m_enable_pfs = pfs_monitor; + + ut_ad(!in_transit_state()); + ret = true; + } + + mysql_mutex_unlock(&m_snapshot_mutex); + return ret; +} + +void Clone_Snapshot::detach() { + mysql_mutex_lock(&m_snapshot_mutex); + + ut_ad(m_num_clones > 0); + ut_ad(!in_transit_state()); + + --m_num_clones; + ut_ad(m_num_clones == 0); + + mysql_mutex_unlock(&m_snapshot_mutex); +} + +bool Clone_Snapshot::is_aborted() const { + mysql_mutex_assert_owner(&m_snapshot_mutex); + return m_aborted; +} + +void Clone_Snapshot::set_abort() { + Mysql_mutex_guard guard(&m_snapshot_mutex); + m_aborted = true; + ib::info() << "Clone Snapshot aborted"; +} + +Clone_Snapshot::State_transit::State_transit(Clone_Snapshot *snapshot, + Snapshot_State new_state) + : m_snapshot(snapshot) { + mysql_mutex_lock(&m_snapshot->m_snapshot_mutex); + + ut_ad(!m_snapshot->in_transit_wait()); + ut_ad(!m_snapshot->in_transit_state()); + + m_snapshot->begin_transit_ddl_wait(); + ut_ad(m_snapshot->in_transit_wait()); + + /* Wait for DDLs blocking clone state transition. */ + m_error = m_snapshot->wait(Wait_type::STATE_BLOCKER, nullptr, false, true); + + if (m_error != 0) { + return; /* purecov: inspected */ + } + + m_snapshot->begin_transit(new_state); + ut_ad(m_snapshot->in_transit_state()); +} + +Clone_Snapshot::State_transit::~State_transit() { + if (m_error == 0) { + m_snapshot->end_transit(); + } + + ut_ad(!m_snapshot->in_transit_state()); + ut_ad(!m_snapshot->in_transit_wait()); + + mysql_mutex_unlock(&m_snapshot->m_snapshot_mutex); +} + +Clone_File_Meta *Clone_Snapshot::get_file_by_index(uint index) { + auto file_ctx = get_file_ctx_by_index(index); + + if (file_ctx == nullptr) { + return nullptr; + } + return file_ctx->get_file_meta(); +} + +Clone_file_ctx *Clone_Snapshot::get_file_ctx_by_index(uint index) { + Clone_file_ctx *file_ctx = nullptr; + + if (m_snapshot_state == CLONE_SNAPSHOT_FILE_COPY || + m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY) { + auto num_data_files = m_data_file_vector.size(); + + if (index < num_data_files) { + file_ctx = m_data_file_vector[index]; + } + + } else if (m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY) { + auto num_redo_files = m_redo_file_vector.size(); + + if (index < num_redo_files) { + file_ctx = m_redo_file_vector[index]; + } + } + + return (file_ctx); +} + +int Clone_Snapshot::iterate_files(File_Cbk_Func &&func) { + int err = 0; + + switch (m_snapshot_state) { + case CLONE_SNAPSHOT_FILE_COPY: + err = iterate_data_files(std::forward(func)); + break; + case CLONE_SNAPSHOT_REDO_COPY: + err = iterate_redo_files(std::forward(func)); + break; + default: + err = 0; + } + return err; +} + +int Clone_Snapshot::iterate_data_files(File_Cbk_Func &&func) { + Mysql_mutex_guard guard(&m_snapshot_mutex); + + for (auto file_ctx : m_data_file_vector) { + auto err = func(file_ctx); + if (err != 0) { + return err; /* purecov: inspected */ + } + } + return 0; +} + +int Clone_Snapshot::iterate_redo_files(File_Cbk_Func &&func) { + for (auto file_ctx : m_redo_file_vector) { + auto err = func(file_ctx); + if (err != 0) { + return err; /* purecov: inspected */ + } + } + return 0; +} + +int Clone_Snapshot::get_next_block(uint chunk_num, uint &block_num, + const Clone_file_ctx *&file_ctx, + uint64_t &data_offset, byte *&data_buf, + uint32_t &data_size, uint64_t &file_size) { + uint64_t start_offset = 0; + const auto file_meta = file_ctx->get_file_meta_read(); + file_size = 0; + + if (m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY) { + /* Copy the page from buffer pool. */ + auto err = get_next_page(chunk_num, block_num, file_ctx, data_offset, + data_buf, data_size, file_size); + return (err); + + } else if (m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY) { + /* For redo copy header and trailer are returned in buffer. */ + + if (chunk_num == (m_num_current_chunks - 1)) { + /* Last but one chunk is the redo header. */ + + if (block_num != 0) { + block_num = 0; + return (0); + } + + ++block_num; + + data_offset = 0; + + data_buf = m_redo_header; + ut_ad(data_buf != nullptr); + + data_size = m_redo_header_size; + + return (0); + + } else if (chunk_num == m_num_current_chunks) { + /* Last chunk is the redo trailer. */ + + if (block_num != 0 || m_redo_trailer_size == 0) { + block_num = 0; + return (0); + } + + ++block_num; + + data_offset = m_redo_trailer_offset; + + data_buf = m_redo_trailer; + ut_ad(data_buf != nullptr); + + data_size = m_redo_trailer_size; + + return (0); + } + + /* This is not header or trailer chunk. Need to get redo + data from archived file. */ + start_offset = (file_meta->m_begin_chunk == 1) ? + m_redo_start_offset : log_t::START_OFFSET; + + /* Dummy redo file entry. Need to send metadata. */ + if (file_meta->m_file_size == 0) { + if (block_num != 0) { + block_num = 0; + return (0); + } + ++block_num; + + data_buf = nullptr; + data_size = 0; + data_offset = 0; + + return (0); + } + } + + /* We have identified the file to transfer data at this point. + Get the data offset for next block to transfer. */ + uint num_blocks; + + data_buf = nullptr; + + uint64_t file_chnuk_num = chunk_num - file_meta->m_begin_chunk; + + /* Offset in pages for current chunk. */ + uint64_t chunk_offset = file_chnuk_num << m_chunk_size_pow2; + + /* Find number of blocks in current chunk. */ + if (chunk_num == file_meta->m_end_chunk) { + /* If it is last chunk, we need to adjust the size. */ + uint64_t size_in_pages; + uint aligned_sz; + + ut_ad(file_meta->m_file_size >= start_offset); + size_in_pages = ut_uint64_align_up(file_meta->m_file_size - start_offset, + UNIV_PAGE_SIZE); + size_in_pages /= UNIV_PAGE_SIZE; + + ut_ad(size_in_pages >= chunk_offset); + size_in_pages -= chunk_offset; + + aligned_sz = static_cast(size_in_pages); + ut_ad(aligned_sz == size_in_pages); + + aligned_sz = ut_calc_align(aligned_sz, block_size()); + + num_blocks = aligned_sz >> m_block_size_pow2; + } else { + num_blocks = blocks_per_chunk(); + } + + /* Current block is the last one. No more blocks in current chunk. */ + if (block_num == num_blocks) { + block_num = 0; + return (0); + } + + ut_ad(block_num < num_blocks); + + /* Calculate the offset of next block. */ + uint64_t block_offset; + + block_offset = static_cast(block_num); + block_offset *= block_size(); + + data_offset = chunk_offset + block_offset; + data_size = block_size(); + + ++block_num; + + /* Convert offset and length in bytes. */ + data_size *= static_cast(UNIV_PAGE_SIZE); + data_offset *= UNIV_PAGE_SIZE; + data_offset += start_offset; + + ut_ad(data_offset < file_meta->m_file_size); + + /* Adjust length for last block in last chunk. */ + if (chunk_num == file_meta->m_end_chunk && block_num == num_blocks) { + ut_ad((data_offset + data_size) >= file_meta->m_file_size); + data_size = static_cast(file_meta->m_file_size - data_offset); + } + +#ifdef UNIV_DEBUG + if (m_snapshot_state == CLONE_SNAPSHOT_REDO_COPY) { + /* Current file is the last redo file */ + auto redo_file_ctx = m_redo_file_vector.back(); + if (file_meta == redo_file_ctx->get_file_meta() && + m_redo_trailer_size != 0) { + /* Should not exceed/overwrite the trailer */ + ut_ad(data_offset + data_size <= m_redo_trailer_offset); + } + } +#endif /* UNIV_DEBUG */ + + return (0); +} + +void Clone_Snapshot::update_block_size(uint buff_size) { + mysql_mutex_lock(&m_snapshot_mutex); + + /* Transfer data block is used only for direct IO. */ + if (m_snapshot_state != CLONE_SNAPSHOT_INIT || fil_system.is_buffered()) { + mysql_mutex_unlock(&m_snapshot_mutex); + return; + } + + /* Try to set block size bigger than the transfer buffer. */ + while (buff_size > (block_size() * UNIV_PAGE_SIZE) && + m_block_size_pow2 < SNAPSHOT_MAX_BLOCK_SIZE_POW2) { + ++m_block_size_pow2; + } + + mysql_mutex_unlock(&m_snapshot_mutex); +} + +uint32_t Clone_Snapshot::get_blocks_per_chunk() const { + Mysql_mutex_guard guard(&m_snapshot_mutex); + uint32_t num_blocks = 0; + + switch (m_snapshot_state) { + case CLONE_SNAPSHOT_PAGE_COPY: + num_blocks = chunk_size(); + break; + + case CLONE_SNAPSHOT_FILE_COPY: + [[fallthrough]]; + + case CLONE_SNAPSHOT_REDO_COPY: + num_blocks = blocks_per_chunk(); + break; + + default: + /* purecov: begin deadcode */ + num_blocks = 0; + break; + /* purecov: end */ + } + return num_blocks; +} + +int Clone_Snapshot::change_state(Clone_Desc_State *state_desc, + Snapshot_State new_state, byte *temp_buffer, + uint temp_buffer_len, Clone_Alert_Func cbk) { + ut_ad(m_snapshot_state != CLONE_SNAPSHOT_NONE); + + int err = 0; + m_num_current_chunks = 0; + + if (!is_copy()) { + err = init_apply_state(state_desc); + return (err); + } + + switch (new_state) { + case CLONE_SNAPSHOT_NONE: + case CLONE_SNAPSHOT_INIT: + err = ER_INTERNAL_ERROR; + my_error(err, MYF(0), "Innodb Clone Snapshot Invalid state"); + ut_d(ut_error); + break; + + case CLONE_SNAPSHOT_FILE_COPY: + ib::info() << "Clone State BEGIN FILE COPY"; + + err = init_file_copy(new_state); + + DEBUG_SYNC_C("clone_start_page_archiving"); + DBUG_EXECUTE_IF("clone_crash_during_page_archiving", DBUG_SUICIDE();); + break; + + case CLONE_SNAPSHOT_PAGE_COPY: + ib::info() << "Clone State BEGIN PAGE COPY"; + + err = init_page_copy(new_state, temp_buffer, temp_buffer_len); + + DEBUG_SYNC_C("clone_start_redo_archiving"); + break; + + case CLONE_SNAPSHOT_REDO_COPY: + /* Defer Snapshot state transfer to Clone_Handle::snapshot(). */ + DEBUG_SYNC_C("clone_donor_after_saving_dynamic_metadata"); + break; + + case CLONE_SNAPSHOT_DONE: { + ib::info() << "Clone State DONE "; + + State_transit transit_guard(this, new_state); + m_monitor.init_state(PSI_NOT_INSTRUMENTED, m_enable_pfs); + + m_redo_ctx.release(); + + err = transit_guard.get_error(); + break; + } + } + return err; +} + +Clone_file_ctx *Clone_Snapshot::get_file(Clone_File_Vec &file_vector, + uint32_t chunk_num, + uint32_t start_index) { + Clone_file_ctx *current_file = nullptr; + uint idx; + + auto num_files = file_vector.size(); + + /* Scan through the file vector matching chunk number. */ + for (idx = start_index; idx < num_files; idx++) { + current_file = file_vector[idx]; + auto file_meta = current_file->get_file_meta(); + + ut_ad(chunk_num >= file_meta->m_begin_chunk); + + if (chunk_num <= file_meta->m_end_chunk) { + break; + } + } + + return (current_file); +} + +void Clone_Snapshot::skip_deleted_blocks(uint32_t chunk_num, + uint32_t &block_num) { + /* For file copy entire chunk can be ignored because chunk + doesn't span across files. */ + if (m_snapshot_state != CLONE_SNAPSHOT_PAGE_COPY) { + ut_ad(m_snapshot_state == CLONE_SNAPSHOT_FILE_COPY); + block_num = 0; + return; + } + + const auto *cur_file_ctx = get_page_file_ctx(chunk_num, block_num); + const auto *next_file_ctx = cur_file_ctx; + + ut_ad(cur_file_ctx->deleted()); + + /* Skip over the deleted file pages of current file context. */ + while (next_file_ctx == cur_file_ctx) { + ++block_num; + next_file_ctx = get_page_file_ctx(chunk_num, block_num); + + /* End of current chunk. */ + if (next_file_ctx == nullptr || block_num >= chunk_size()) { + block_num = 0; + break; + } + } +} + +int Clone_Snapshot::get_next_page(uint chunk_num, uint &block_num, + const Clone_file_ctx *&file_ctx, + uint64_t &data_offset, byte *&data_buf, + uint32_t &data_size, uint64_t &file_size) { + ut_ad(data_size >= UNIV_PAGE_SIZE); + file_size = 0; + + ut_ad(file_ctx->is_pinned()); + ut_ad(block_num < chunk_size()); + + /* For "page copy", each block is a page. */ + uint32_t page_index = chunk_size() * (chunk_num - 1); + page_index += block_num; + + ut_a(page_index < m_page_vector.size()); + auto clone_page = m_page_vector[page_index]; + + ++block_num; + + /* Get the data file for current page. */ + + auto file_meta = file_ctx->get_file_meta_read(); + ut_ad(file_meta->m_space_id == clone_page.m_space_id); + + /* Data offset could be beyond 32 BIT integer. */ + data_offset = static_cast(clone_page.m_page_no); + uint32_t page_size= fil_space_t::physical_size(file_meta->m_fsp_flags); + data_offset*= page_size; + + auto file_index = file_meta->m_file_index; + + /* Check if the page belongs to other nodes of the tablespace. */ + while (num_data_files() > file_index + 1) { + const auto file_next = m_data_file_vector[file_index + 1]; + const auto file_meta_next = file_next->get_file_meta(); + + /* Next node belongs to same tablespace and data offset + exceeds current node size */ + if (file_meta_next->m_space_id == file_meta->m_space_id && + data_offset >= file_meta->m_file_size) { + data_offset -= file_meta->m_file_size; + file_meta = file_meta_next; + file_index = file_meta->m_file_index; + file_ctx = file_next; + } else { + break; + } + } + + /* Get page from buffer pool. */ + page_id_t page_id(clone_page.m_space_id, clone_page.m_page_no); + + auto err = + get_page_for_write(page_id, page_size, file_ctx, data_buf, data_size); + + /* Update size from space header page. */ + if (clone_page.m_page_no == 0) { + auto space_size = fsp_header_get_field(data_buf, FSP_SIZE); + + auto size_bytes= static_cast(space_size); + + size_bytes*= page_size; + + if (file_meta->m_file_size < size_bytes) { + file_size = size_bytes; + } + } + return (err); +} + +void Clone_Snapshot::page_compress_encrypt(const Clone_File_Meta *file_meta, + byte *&page_data, uint32_t data_size, + ulint zip_size, bool full_crc32, + bool compress, bool encrypt, + uint32_t page_no) +{ + auto encrypted_data= page_data + data_size; + + /* Do transparent page compression if needed. */ + if (compress) + { + auto compressed_data= page_data + data_size; + memset(compressed_data, 0, data_size); + + auto len= fil_page_compress(page_data, compressed_data, + file_meta->m_fsp_flags, file_meta->m_fsblk_size, encrypt); + + if (len > 0) { + encrypted_data= page_data; + page_data= compressed_data; + } + } + + if (encrypt) + { + memset(encrypted_data, 0, data_size); + /* TODO: Pass encryption metadata. */ + ut_ad(false); + page_data= fil_encrypt_buf(nullptr, file_meta->m_space_id, page_no, + page_data, zip_size, encrypted_data, full_crc32); + } +} + +void Clone_Snapshot::page_update_for_flush(ulint zip_size, byte *&page_data, + bool full_crc32) { + /* For compressed table, must copy the compressed page. */ + if (zip_size) { + page_zip_des_t page_zip; + + auto data_size= zip_size; + page_zip_set_size(&page_zip, data_size); + page_zip.data = page_data; + ut_d(page_zip.m_start = 0); + page_zip.m_end = 0; + page_zip.n_blobs = 0; + page_zip.m_nonempty = false; + + buf_flush_init_for_writing(nullptr, page_data, &page_zip, full_crc32); + } else { + buf_flush_init_for_writing(nullptr, page_data, nullptr, full_crc32); + } +} + +int Clone_Snapshot::get_page_for_write(const page_id_t &page_id, + uint32_t page_size, + const Clone_file_ctx *file_ctx, + byte *&page_data, uint &data_size) +{ + auto file_meta = file_ctx->get_file_meta_read(); + + mtr_t mtr; + mtr_start(&mtr); + + ut_ad(data_size >= 2 * page_size); + + data_size= page_size; + auto zip_size= fil_space_t::zip_size(file_meta->m_fsp_flags); + + /* Space header page is modified with SX latch while extending. Also, + we would like to serialize with page flush to disk. */ + dberr_t error= DB_SUCCESS; + auto block = + buf_page_get_gen(page_id, zip_size, RW_SX_LATCH, nullptr, + BUF_GET_POSSIBLY_FREED, &mtr, &error); + if (!block) + { + if (error == DB_SUCCESS) + { + /* In case of freed page, fill the page with full of zeroes */ + memcpy(page_data, field_ref_zero, data_size); + return 0; + } + /* In case of corruption, return error */ + my_error(ER_INTERNAL_ERROR, MYF(0), "Innodb Clone Corrupt Page"); + return ER_INTERNAL_ERROR; + } + auto bpage = &block->page; + + ut_ad(!fsp_is_system_temporary(bpage->id().space())); + /* Get oldest and newest page modification LSN for dirty page. */ + auto oldest_lsn = bpage->oldest_modification(); + + bool page_is_dirty= (oldest_lsn > 0); + byte *src_data= buf_block_get_frame(block); + + if (bpage->zip.data) + /* If the page is not dirty, then zip descriptor always has the latest + flushed page copy with LSN and checksum set properly. If the page is + dirty, the latest modified page is in uncompressed form for uncompressed + page types. The LSN in such case is to be taken from block newest LSN and + checksum needs to be recalculated. */ + if (!page_is_dirty || page_is_uncompressed_type(src_data)) + src_data= bpage->zip.data; + + memcpy(page_data, src_data, data_size); + + auto cur_lsn = log_sys.get_lsn_approx(); + auto frame_lsn= + static_cast(mach_read_from_8(page_data + FIL_PAGE_LSN)); + + /* First page of a encrypted tablespace. */ + /* TODO: Encryption metadata: Key*/ + ut_ad(!file_meta->can_encrypt()); + + /* If the page is not dirty but frame LSN is zero, it could be half + initialized page left from incomplete operation. Assign valid LSN and checksum + before copy. */ + if (frame_lsn == 0 && oldest_lsn == 0) { + page_is_dirty= true; + frame_lsn= cur_lsn; + mach_write_to_8(page_data + FIL_PAGE_LSN, frame_lsn); + } + + bool full_crc32= fil_space_t::full_crc32(file_meta->m_fsp_flags); + auto page_no= page_id.page_no(); + auto page_type= fil_page_get_type(page_data); + + bool compression= file_meta->can_compress(); + bool encryption= file_meta->can_encrypt(); + + /* Disable compression and encryption based on page number. */ + if (page_no == 0 + || (page_id.space() == TRX_SYS_SPACE && page_no == TRX_SYS_PAGE_NO)) { + compression= false; + encryption= false; + } + + /* Disable compression based on page type: fil_page_compress() */ + if (page_type == 0 || page_type == FIL_PAGE_TYPE_FSP_HDR + || page_type == FIL_PAGE_TYPE_XDES + || page_type == FIL_PAGE_PAGE_COMPRESSED) + compression= false; + + /* Disable encryption based on page type: fil_space_encrypt_valid_page_type() */ + if (page_type == FIL_PAGE_TYPE_FSP_HDR || page_type == FIL_PAGE_TYPE_XDES + || (page_type == FIL_PAGE_RTREE && !full_crc32)) + encryption= false; + + bool encrypt_before_checksum= !zip_size && full_crc32; + + if (encrypt_before_checksum && (compression || encryption)) + { + page_is_dirty= true; + page_compress_encrypt(file_meta, page_data, data_size, zip_size, + full_crc32, compression, encryption, page_no); + } + + /* If page is dirty, we need to set checksum and page LSN. */ + if (page_is_dirty) { + ut_ad(frame_lsn > 0); + page_update_for_flush(zip_size, page_data, full_crc32); + } + + /* TODO: Validate checksum after updating page. */ + // BlockReporter reporter(false, page_data, page_size, false); + + const auto page_lsn= + static_cast(mach_read_from_8(page_data + FIL_PAGE_LSN)); + + const auto page_checksum = static_cast( + mach_read_from_4(page_data + FIL_PAGE_SPACE_OR_CHKSUM)); + + int err= 0; + if (/* reporter.is_corrupted() || */ page_lsn > cur_lsn || + (page_checksum != 0 && page_lsn == 0)) { + my_error(ER_INTERNAL_ERROR, MYF(0), "Innodb Clone Corrupt Page"); + err = ER_INTERNAL_ERROR; + ut_d(ut_error); + } + + if (!encrypt_before_checksum && (compression || encryption)) + page_compress_encrypt(file_meta, page_data, data_size, zip_size, + full_crc32, compression, encryption, page_no); + mtr_commit(&mtr); + return err; +} + +uint32_t Clone_Snapshot::get_max_blocks_pin() const { + return (m_snapshot_state == CLONE_SNAPSHOT_PAGE_COPY) ? S_MAX_PAGES_PIN + : S_MAX_BLOCKS_PIN; +} + +Clone_file_ctx *Clone_Snapshot::get_file_ctx(uint32_t chunk_num, + uint32_t block_num, + uint32_t hint_index) { + Clone_file_ctx *file = nullptr; + + switch (m_snapshot_state) { + case CLONE_SNAPSHOT_FILE_COPY: + file = get_data_file_ctx(chunk_num, hint_index); + break; + case CLONE_SNAPSHOT_PAGE_COPY: + file = get_page_file_ctx(chunk_num, block_num); + break; + case CLONE_SNAPSHOT_REDO_COPY: + file = get_redo_file_ctx(chunk_num, hint_index); + break; + default: + ut_d(ut_error); /* purecov: deadcode */ + } + return file; +} + +Clone_file_ctx *Clone_Snapshot::get_data_file_ctx(uint32_t chunk_num, + uint32_t hint_index) { + return get_file(m_data_file_vector, chunk_num, hint_index); +} + +Clone_file_ctx *Clone_Snapshot::get_redo_file_ctx(uint32_t chunk_num, + uint32_t hint_index) { + /* Last but one chunk is redo header */ + if (chunk_num == (m_num_current_chunks - 1)) { + return m_redo_file_vector.front(); + } + /* Last chunk is the redo trailer. */ + if (chunk_num == m_num_current_chunks) { + return m_redo_file_vector.back(); + } + return get_file(m_redo_file_vector, chunk_num, hint_index); +} + +Clone_file_ctx *Clone_Snapshot::get_page_file_ctx(uint32_t chunk_num, + uint32_t block_num) { + /* Check if block is beyond the current chunk. */ + if (block_num >= chunk_size()) { + ut_ad(block_num == chunk_size()); + return nullptr; + } + + auto page_index = chunk_size() * (chunk_num - 1); + page_index += block_num; + + /* Check if all blocks are over. For last chunk, actual number of blocks + could be less than chunk_size. */ + if (page_index >= m_page_vector.size()) { + ut_ad(page_index == m_page_vector.size()); + return nullptr; + } + + auto clone_page = m_page_vector[page_index]; + auto file_index = m_data_file_map[clone_page.m_space_id]; + if (file_index == 0) { + /* purecov: begin deadcode */ + ut_d(ut_error); + return nullptr; + /* purecov: end */ + } + --file_index; + + auto page_file = get_file_ctx_by_index(file_index); + +#ifdef UNIV_DEBUG + auto file_meta = page_file->get_file_meta(); + ut_ad(file_meta->m_space_id == clone_page.m_space_id); +#endif // UNIV_DEBUG + + return page_file; +} + +void Clone_file_ctx::get_file_name(std::string &name) const { + name.assign(m_meta.m_file_name); + + /* Add file name extension. */ + switch (m_extension) { + case Extension::REPLACE: + name.append(CLONE_INNODB_REPLACED_FILE_EXTN); + break; + + case Extension::DDL: + name.append(CLONE_INNODB_DDL_FILE_EXTN); + break; + + case Extension::NONE: + default: + break; + } +} + +bool Clone_Snapshot::begin_ddl_state(Clone_notify::Type type, space_id_t space, + bool no_wait, bool check_intr, + int &error) { + Mysql_mutex_guard guard(&m_snapshot_mutex); + error = 0; + bool blocked = false; + + for (;;) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + auto state = get_state(); + + switch (state) { + case CLONE_SNAPSHOT_NONE: + /* purecov: begin deadcode */ + /* Clone must have started at this point. */ + ut_d(ut_error); + break; + /* purecov: end */ + + case CLONE_SNAPSHOT_INIT: + /* Fall through. */ + case CLONE_SNAPSHOT_FILE_COPY: + /* Allow clone to enter next stage only after the DDL file operation + is complete. */ + blocked = block_state_change(type, space, no_wait, check_intr, error); + mysql_mutex_assert_owner(&m_snapshot_mutex); + + if (error != 0) { + /* We should not have blocked in case of error but it is not fatal. */ + ut_ad(!blocked); + break; + } + + if (state != get_state()) { + /* purecov: begin inspected */ + /* State is modified. Start again and recheck. This is safe + as clone has to eventually exit from the above two states. */ + ut_ad(!blocked); + continue; + /* purecov: end */ + } + + ut_ad(blocked); + + if (state == CLONE_SNAPSHOT_FILE_COPY) { + error = begin_ddl_file(type, space, no_wait, check_intr); + } + break; + + case CLONE_SNAPSHOT_PAGE_COPY: + /* 1. Bulk operation currently need to wait if clone has entered page + copy. This is because bulk changes don't generate any redo log. + 2. We don't let new encryption alter to begin during page copy state. + We currently cannot handle encryption key in redo log which is + encrypted by donor master key. */ + ut_ad(!blocked); + /* Try to block state change. If state is already modified then nothing + to do as the next states don't require blocking. */ + blocked = block_state_change(type, space, no_wait, check_intr, error); + if (error != 0 || state != get_state()) { + /* We should not have blocked in case of error but it is not fatal. */ + ut_ad(!blocked); + break; + } + ut_ad(blocked); + + error = begin_ddl_file(type, space, no_wait, check_intr); + break; + case CLONE_SNAPSHOT_REDO_COPY: + /* Snapshot end point is already taken. This changes are not part of + snapshot. */ + break; + case CLONE_SNAPSHOT_DONE: + /* Clone has already finished. */ + break; + default: + /* purecov: begin deadcode */ + ut_d(ut_error); + break; + /* purecov: end */ + } + break; + } /* purecov: inspected */ + + /* Unblock clone, in case of error. */ + if (blocked && error != 0) { + /* purecov: begin inspected */ + unblock_state_change(); + blocked = false; + /* purecov: end */ + } + return blocked; +} + +void Clone_Snapshot::end_ddl_state(Clone_notify::Type type, space_id_t space) { + /* Caller is responsible to call if we have blocked state change. */ + Mysql_mutex_guard guard(&m_snapshot_mutex); + auto state = get_state(); + + if (state == CLONE_SNAPSHOT_FILE_COPY || state == CLONE_SNAPSHOT_PAGE_COPY) { + end_ddl_file(type, space); + } + unblock_state_change(); +} + +void Clone_Snapshot::get_wait_mesg(Wait_type wait_type, std::string &info, + std::string &error) { + switch (wait_type) { + case Wait_type::STATE_TRANSIT_WAIT: + break; + case Wait_type::STATE_TRANSIT: + info.assign("DDL waiting for clone state transition"); + error.assign("DDL wait for clone state transition timed out"); + break; + case Wait_type::STATE_END_PAGE_COPY: + info.assign("DDL waiting for Clone PAGE COPY to finish"); + error.assign("DDL wait for Clone PAGE COPY timed out"); + break; + case Wait_type::STATE_BLOCKER: + info.assign("Clone state transition waiting for DDL file operation"); + error.assign( + "Clone state transition wait for DDL file operation timed out"); + break; + case Wait_type::DATA_FILE_WAIT: + info.assign("DDL waiting for clone threads to exit from previous wait"); + error.assign( + "DDL wait for clone threads to exit from wait state timed out"); + break; + case Wait_type::DATA_FILE_CLOSE: + info.assign("DDL waiting for clone to close the open data file"); + error.assign("DDL wait for clone data file close timed out"); + break; + case Wait_type::DDL_FILE_OPERATION: + info.assign("Clone waiting for DDL file operation"); + error.assign("Clone wait for DDL file operation timed out"); + break; + default: + ut_d(ut_error); /* purecov: deadcode */ + } +} + +const char *Clone_Snapshot::wait_string(Wait_type wait_type) const { + const char *wait_info = nullptr; + + switch (wait_type) { + /* DDL waiting for clone state transition */ + case Wait_type::STATE_TRANSIT_WAIT: + [[fallthrough]]; + case Wait_type::STATE_TRANSIT: + wait_info = "Waiting for clone state transition"; + break; + + /* DDL waiting till Clone PAGE COPY state is over. */ + case Wait_type::STATE_END_PAGE_COPY: + wait_info = "Waiting for clone PAGE_COPY state"; + break; + + /*DDL waiting for clone file operation. */ + case Wait_type::DATA_FILE_WAIT: + [[fallthrough]]; + case Wait_type::DATA_FILE_CLOSE: + wait_info = "Waiting for clone to close files"; + break; + + /* Clone waiting for DDL. */ + case Wait_type::DDL_FILE_OPERATION: + wait_info = "Waiting for ddl file operation"; + break; + + case Wait_type::STATE_BLOCKER: + wait_info = "Waiting for ddl before state transition"; + [[fallthrough]]; + + default: + break; + } + + return wait_info; +} + +int Clone_Snapshot::wait(Wait_type wait_type, const Clone_file_ctx *ctx, + bool no_wait, bool check_intr) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + + std::string info_mesg; + std::string error_mesg; + + get_wait_mesg(wait_type, info_mesg, error_mesg); + + auto wait_cond = [&](bool alert, bool &wait) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + bool early_exit = false; + + switch (wait_type) { + case Wait_type::STATE_TRANSIT_WAIT: + wait = in_transit_wait(); + /* For state transition wait by DDL, exit on alert to avoid + possible deadlock between DDLs. */ + early_exit = true; + break; + case Wait_type::STATE_TRANSIT: + wait = in_transit_state(); + break; + case Wait_type::STATE_END_PAGE_COPY: + /* If clone has aborted, don't wait for state to end. */ + wait = !is_aborted() && (get_state() == CLONE_SNAPSHOT_PAGE_COPY); + DBUG_EXECUTE_IF("clone_ddl_abort_wait_page_copy", { + if (wait) { + my_error(ER_INTERNAL_ERROR, MYF(0), "Simulated Clone DDL error"); + return ER_INTERNAL_ERROR; + } + }); + break; + case Wait_type::STATE_BLOCKER: + wait = (m_num_blockers > 0); + break; + case Wait_type::DATA_FILE_WAIT: + wait = ctx->is_waiting(); + early_exit = true; + break; + case Wait_type::DATA_FILE_CLOSE: + wait = ctx->is_pinned(); + break; + case Wait_type::DDL_FILE_OPERATION: + wait = blocks_clone(ctx); + break; + default: + /* purecov: begin deadcode */ + wait = false; + ut_d(ut_error); + /* purecov: end */ + } + + if (wait) { + if (no_wait || (alert && early_exit)) { + return ER_STATEMENT_TIMEOUT; /* purecov: inspected */ + } + + if (alert) { + ib::info() << info_mesg; /* purecov: tested */ + } + + if (check_intr && thd_killed(current_thd)) { + /* For early exit the caller would ignore error. */ + if (!early_exit) { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + } + return ER_QUERY_INTERRUPTED; + } + } + return 0; + }; + + /* SET THD information string to display waiting state in PROCESS LIST. */ + Clone_Sys::Wait_stage wait_guard(wait_string(wait_type)); + + bool is_timeout = false; + int err = 0; + + /* Increase the defaults to wait more while waiting for page copy state. */ + if (wait_type == Wait_type::STATE_END_PAGE_COPY) { + /* Generate alert message every 5 minutes. */ + Clone_Sec alert_interval(Clone_Min(5)); + /* Wait for 2 hours for clone to finish. */ + Clone_Sec time_out(Clone_Min(120)); + + err = Clone_Sys::wait(CLONE_DEF_SLEEP, time_out, alert_interval, wait_cond, + &m_snapshot_mutex, is_timeout); + } else { + err = Clone_Sys::wait_default(wait_cond, &m_snapshot_mutex, is_timeout); + } + + if (!err && is_timeout) { + /* purecov: begin deadcode */ + err = ER_INTERNAL_ERROR; + my_error(err, MYF(0), error_mesg.c_str()); + ut_d(ut_error); + /* purecov: end */ + } + return err; +} + +bool Clone_Snapshot::block_state_change(Clone_notify::Type type, + space_id_t space, bool no_wait, + bool check_intr, int &error) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + + /* For undo DDL, there could be recursive notification for file create + and drop which are !undo_ddl_ntfn. For such notifications we don't need + to wait for clone as we must have already blocked it. */ + bool wait_clone= !srv_is_undo_tablespace(space); + + /* If no wait option is used, override any waiting clone. Used for undo + truncate background currently. We don't want to block purge threads. */ + if (no_wait) { + wait_clone = false; + } + + auto saved_state = get_state(); + + /* Wait for the waiting clone. That is if clone is blocked by other DDL and + waiting. This is an attempt to prevent starvation of clone by DDLs. We wait + here for limited time to prevent possible deadlock between DDLs. + e.g. DDL-2 <- DDL-1 (Critical section) <- Clone <- DDL-2. */ + if (wait_clone) { + static_cast( + wait(Wait_type::STATE_TRANSIT_WAIT, nullptr, false, false)); + mysql_mutex_assert_owner(&m_snapshot_mutex); + if (saved_state != get_state()) { + /* State is modified. Return for possible recheck. */ + return false; /* purecov: inspected */ + } + } + + /* Wait for state transition to get over. */ + error = wait(Wait_type::STATE_TRANSIT, nullptr, no_wait, check_intr); + + if (error != 0) { + return false; + } + + mysql_mutex_assert_owner(&m_snapshot_mutex); + if (saved_state != get_state()) { + /* State is modified. Return for possible recheck. */ + return false; /* purecov: inspected */ + } + + mysql_mutex_assert_owner(&m_snapshot_mutex); + ++m_num_blockers; + + return true; +} + +inline void Clone_Snapshot::unblock_state_change() { + mysql_mutex_assert_owner(&m_snapshot_mutex); + --m_num_blockers; +} + +Clone_file_ctx::State Clone_Snapshot::get_target_file_state( + Clone_notify::Type type, bool begin) { + return Clone_file_ctx::State::NONE; +} + +bool Clone_Snapshot::blocks_clone(const Clone_file_ctx *file_ctx) { + bool block = false; + auto clone_state = get_state(); + + switch (clone_state) { + case CLONE_SNAPSHOT_FILE_COPY: + /* Block clone operation for both rename and delete operation + as we directly access the file. */ + block = file_ctx->modifying(); + break; + case CLONE_SNAPSHOT_PAGE_COPY: + /* Block clone operation only if deleting. In page copy state we don't + bother about space/file rename. If the page is not found in buffer pool, + it would need to be read from underlying file but this IO needs to be + synchronized with file operation irrespective of clone. */ + block = file_ctx->deleting(); + break; + default: + block = false; + break; + } + return block; +} + +int Clone_Snapshot::begin_ddl_file(Clone_notify::Type type, space_id_t space, + bool no_wait, bool check_intr) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + ut_ad(get_state() == CLONE_SNAPSHOT_FILE_COPY || + get_state() == CLONE_SNAPSHOT_PAGE_COPY); + + auto target_state = get_target_file_state(type, true); + + /* The type doesn't need any file operation. */ + if (target_state == Clone_file_ctx::State::NONE) { + return 0; + } + auto count = m_data_file_map.count(space); + + /* The space is added concurrently and then modified again. */ + if (count == 0) { + return 0; + } + /* If the space is already added for clone, we would have that in the map + with a valid file index (starts from 1). */ + auto file_index = m_data_file_map[space]; + + if (file_index == 0) { + /* purecov: begin deadcode */ + ut_d(ut_error); + return 0; + /* purecov: end */ + } + --file_index; + + auto file_ctx = get_file_ctx_by_index(file_index); + + auto saved_state = file_ctx->m_state.load(); + + ut_ad(saved_state != Clone_file_ctx::State::DROPPING); + ut_ad(saved_state != Clone_file_ctx::State::RENAMING); + ut_ad(saved_state != Clone_file_ctx::State::DROPPED); + + file_ctx->m_state.store(target_state); + + /* Wait for all data files to be closed by clone threads. */ + if (blocks_clone(file_ctx)) { + auto err = wait(Wait_type::DATA_FILE_CLOSE, file_ctx, no_wait, check_intr); + + if (err != 0) { + /* purecov: begin inspected */ + file_ctx->m_state.store(saved_state); + return err; + /* purecov: end */ + } + } + return 0; +} + +void Clone_Snapshot::end_ddl_file(Clone_notify::Type type, space_id_t space) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + ut_ad(get_state() == CLONE_SNAPSHOT_FILE_COPY || + get_state() == CLONE_SNAPSHOT_PAGE_COPY); + + auto target_state = get_target_file_state(type, false); + + /* The type doesn't need any file operation. */ + if (target_state == Clone_file_ctx::State::NONE) { + return; + } + auto count = m_data_file_map.count(space); + + /* The space is added concurrently and then modified again. */ + if (count == 0) { + return; + } + uint32_t file_index = m_data_file_map[space]; + + if (file_index == 0) { + /* purecov: begin deadcode */ + ut_d(ut_error); + return; + /* purecov: end */ + } + --file_index; + + auto file_ctx = get_file_ctx_by_index(file_index); + auto file_meta = file_ctx->get_file_meta(); + + file_ctx->set_ddl(get_next_state()); + + bool blocking_clone = blocks_clone(file_ctx); + + /* We need file handling for drop and rename. */ + file_meta->m_renamed = true; + file_ctx->m_state.store(target_state); + + if (blocking_clone) { + auto fil_space = fil_space_get(space); + + ut_ad(UT_LIST_GET_LEN(fil_space->chain) == 1); + + auto node= UT_LIST_GET_FIRST(fil_space->chain); + build_file_name(file_meta, node->name); + + /* Wait for any previously waiting clone threads to restart. This is to + avoid starvation of clone by repeated renames. We ignore any error. Although + not expected there is no functional impact of a timeout here. */ + static_cast(wait(Wait_type::DATA_FILE_WAIT, file_ctx, false, false)); + } +} + +bool Clone_Snapshot::update_deleted_state(Clone_file_ctx *file_ctx) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + + if (file_ctx->m_state == Clone_file_ctx::State::DROPPED_HANDLED) { + return false; + } + + ut_ad(file_ctx->m_state == Clone_file_ctx::State::DROPPED); + /* The deleted file to be handled by current task. Set the + state here so that other tasks can ignore the deleted file. */ + file_ctx->m_state = Clone_file_ctx::State::DROPPED_HANDLED; + return true; +} + +int Clone_Snapshot::pin_file(Clone_file_ctx *file_ctx, bool &handle_delete) { + handle_delete = false; + file_ctx->pin(); + + /* Quick return without acquiring mutex if no DDL. */ + if (!blocks_clone(file_ctx)) { + /* Check and update deleted state. */ + if (file_ctx->deleted()) { + Mysql_mutex_guard guard(&m_snapshot_mutex); + handle_delete = update_deleted_state(file_ctx); + } + return 0; + } + file_ctx->unpin(); + + Mysql_mutex_guard guard(&m_snapshot_mutex); + + if (!blocks_clone(file_ctx)) { + /* purecov: begin inspected */ + file_ctx->pin(); + /* Check and update deleted state. */ + if (file_ctx->deleted()) { + handle_delete = update_deleted_state(file_ctx); + } + return 0; + /* purecov: end */ + } + + file_ctx->begin_wait(); + + /* Wait for DDL file operation to complete. */ + auto err = wait(Wait_type::DDL_FILE_OPERATION, file_ctx, false, true); + + if (err == 0) { + file_ctx->pin(); + /* Check and update deleted state. */ + if (file_ctx->deleted()) { + handle_delete = update_deleted_state(file_ctx); + } + } + + file_ctx->end_wait(); + return err; +} diff --git a/storage/innobase/dict/dict0load.cc b/storage/innobase/dict/dict0load.cc index 5fa670648c6b2..b7ea49c2ef486 100644 --- a/storage/innobase/dict/dict0load.cc +++ b/storage/innobase/dict/dict0load.cc @@ -492,8 +492,6 @@ dict_sys_tables_rec_check( const byte* field; ulint len; - ut_ad(dict_sys.locked()); - if (rec_get_n_fields_old(rec) != DICT_NUM_FIELDS__SYS_TABLES) { return("wrong number of columns in SYS_TABLES record"); } @@ -872,6 +870,75 @@ static uint32_t dict_find_max_space_id(btr_pcur_t *pcur, mtr_t *mtr) return max_space_id; } +void dict_load_spaces_no_ddl() +{ + btr_pcur_t pcur; + mtr_t mtr; + mtr.start(); + + for (const rec_t *rec= dict_startscan_system(&pcur, &mtr, + dict_sys.sys_tables); rec; + rec= dict_getnext_system_low(&pcur, &mtr)) + { + /* If a table record is not useable, ignore it and continue. */ + if (dict_sys_tables_rec_check(rec) || rec_get_deleted_flag(rec, 0)) + continue; + + const byte *field= nullptr; + ulint len= 0; + + /* 1. Extract space ID. */ + field= rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__SPACE, &len); + ut_ad(len == 4); + auto space_id= mach_read_from_4(field); + + mysql_mutex_lock(&fil_system.mutex); + auto space= fil_space_get_by_id(space_id); + mysql_mutex_unlock(&fil_system.mutex); + + if (space) + continue; + + /* 2. Extract space flags. */ + field= rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__TYPE, &len); + ut_ad(len == 4); + auto type= mach_read_from_4(field); + + field= rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__N_COLS, &len); + ut_a(len == 4); + auto n_cols= mach_read_from_4(field); + + const bool redundant= (0 == (n_cols & DICT_N_COLS_COMPACT)); + auto flags= dict_sys_tables_type_to_tf(type, !redundant); + ut_ad(dict_sys_tables_type_valid(type, !redundant)); + + /* 3. Extract table name. */ + auto t_name= reinterpret_cast( + rec_get_nth_field_old(rec, DICT_FLD__SYS_TABLES__NAME, &len)); + const span name{t_name, len}; + + table_name_t table_name(const_cast(name.data())); + auto filepath= fil_make_filepath(nullptr, table_name, IBD, false); + dict_sys.lock(SRW_LOCK_CALL); + + /* Check again after acquiring dictionary lock. */ + mysql_mutex_lock(&fil_system.mutex); + space= fil_space_get_by_id(space_id); + mysql_mutex_unlock(&fil_system.mutex); + + if (!space) + space= fil_ibd_open(space_id, dict_tf_to_fsp_flags(flags), + fil_space_t::VALIDATE_NOTHING, name, filepath); + dict_sys.unlock(); + + if (!space) + ib::error() << "Clone Error opening space: " << name.data() + << " File: " << filepath; + ut_free(filepath); + } + mtr.commit(); +} + /** Check MAX(SPACE) FROM SYS_TABLES and store it in fil_system. Open each data file if an encryption plugin has been loaded. diff --git a/storage/innobase/fil/fil0crypt.cc b/storage/innobase/fil/fil0crypt.cc index dc5bd3b1229ec..197a61cfb5709 100644 --- a/storage/innobase/fil/fil0crypt.cc +++ b/storage/innobase/fil/fil0crypt.cc @@ -2400,6 +2400,30 @@ void fil_crypt_total_stat(fil_crypt_stat_t *stat) mysql_mutex_unlock(&crypt_stat_mutex); } +dberr_t Fil_iterator::iterate(Function &&f) +{ + dberr_t err= DB_SUCCESS; + mysql_mutex_lock(&fil_system.mutex); + for (fil_space_t &space : fil_system.space_list) + { + if (space.is_temporary()) + continue; + + /* If the space is being dropped, it can be skipped. */ + if (!space.acquire_if_not_stopped()) + continue; + + auto node= UT_LIST_GET_FIRST(space.chain); + err= f(node); + + while (err == DB_SUCCESS && (node= UT_LIST_GET_NEXT(chain, node))) + err= f(node); + space.release(); + } + mysql_mutex_unlock(&fil_system.mutex); + return err; +} + #endif /* UNIV_INNOCHECKSUM */ /** diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index 151c0268baba8..9e9753b75d0da 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -3277,6 +3277,22 @@ fil_space_t::name_type fil_space_t::name() const noexcept return name_type{path, len}; } +bool fil_space_t::is_encrypted() const +{ + ut_ad(referenced()); + if (!crypt_data) + return false; + + mysql_mutex_lock(&crypt_data->mutex); + bool encrypted= !crypt_data->not_encrypted() + && crypt_data->type != CRYPT_SCHEME_UNENCRYPTED + && (!crypt_data->is_default_encryption() + || srv_encrypt_tables); + + mysql_mutex_unlock(&crypt_data->mutex); + return encrypted; +} + #ifdef UNIV_DEBUG fil_space_t *fil_space_t::next_in_space_list() noexcept diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 719fec2691d70..3237c2e5e773e 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -72,6 +72,7 @@ this program; if not, write to the Free Software Foundation, Inc., #include "buf0buf.h" #include "buf0flu.h" #include "buf0lru.h" +#include "clone0api.h" #include "dict0boot.h" #include "dict0load.h" #include "dict0crea.h" @@ -617,7 +618,8 @@ static PSI_thread_info all_innodb_threads[] = { {&page_cleaner_thread_key, "page_cleaner", 0}, {&trx_rollback_clean_thread_key, "trx_rollback", 0}, {&page_encrypt_thread_key, "page_encrypt", 0}, - {&thread_pool_thread_key,"ib_tpool_worker", 0} + {&thread_pool_thread_key,"ib_tpool_worker", 0}, + {&archiver_thread_key,"ib_archiver", 0} }; # endif /* UNIV_PFS_THREAD */ @@ -626,7 +628,9 @@ static PSI_thread_info all_innodb_threads[] = { performance schema instrumented if "UNIV_PFS_IO" is defined */ static PSI_file_info all_innodb_files[] = { PSI_KEY(innodb_data_file), - PSI_KEY(innodb_temp_file) + PSI_KEY(innodb_temp_file), + PSI_KEY(innodb_arch_file), + PSI_KEY(innodb_clone_file) }; # endif /* UNIV_PFS_IO */ #endif /* HAVE_PSI_INTERFACE */ @@ -4173,6 +4177,19 @@ static int innodb_init(void* p) innobase_hton->update_optimizer_costs= innobase_update_optimizer_costs; +#ifndef EMBEDDED_LIBRARY + /* Clone interfaces. */ + innobase_hton->clone_interface.clone_capability = innodb_clone_get_capability; + + innobase_hton->clone_interface.clone_begin = innodb_clone_begin; + innobase_hton->clone_interface.clone_copy = innodb_clone_copy; + innobase_hton->clone_interface.clone_ack = innodb_clone_ack; + innobase_hton->clone_interface.clone_end = innodb_clone_end; + + innobase_hton->clone_interface.clone_apply_begin = innodb_clone_apply_begin; + innobase_hton->clone_interface.clone_apply = innodb_clone_apply; + innobase_hton->clone_interface.clone_apply_end = innodb_clone_apply_end; +#endif /* EMBEDDED_LIBRARY */ innodb_remember_check_sysvar_funcs(); compile_time_assert(DATA_MYSQL_TRUE_VARCHAR == MYSQL_TYPE_VARCHAR); @@ -18766,6 +18783,16 @@ static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*, " innodb_log_buffer_size=%u", MYF(0), log_sys.buf_size); else { +#ifndef EMBEDDED_LIBRARY + Clone_notify *notifier= new Clone_notify( + Clone_notify::Type::SYSTEM_REDO_RESIZE, UINT32_MAX, true); + if (notifier->failed()) + { + delete notifier; + mysql_mutex_lock(&LOCK_global_system_variables); + return; + } +#endif /* EMBEDDED_LIBRARY */ switch (log_sys.resize_start(*static_cast(save), thd)) { case log_t::RESIZE_NO_CHANGE: break; @@ -18786,6 +18813,7 @@ static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*, break; } + DEBUG_SYNC_C("redo_log_resizing"); set_timespec(abstime, 5); mysql_mutex_lock(&buf_pool.flush_list_mutex); lsn_t resizing= log_sys.resize_in_progress(); @@ -18812,6 +18840,9 @@ static void innodb_log_file_size_update(THD *thd, st_mysql_sys_var*, log_sys.latch.wr_unlock(); } } +#ifndef EMBEDDED_LIBRARY + delete notifier; +#endif /* !EMBEDDED_LIBRARY */ } mysql_mutex_lock(&LOCK_global_system_variables); } diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index 9003d9827d318..c4c90c6d5e501 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -22,6 +22,8 @@ this program; if not, write to the Free Software Foundation, Inc., #endif /* WITH_WSREP */ #include "table.h" +#include "fts0fts.h" +#include "dict0mem.h" /* The InnoDB handler: the interface between MySQL and InnoDB. */ diff --git a/storage/innobase/include/arch0arch.h b/storage/innobase/include/arch0arch.h new file mode 100644 index 0000000000000..7bc7850767bdd --- /dev/null +++ b/storage/innobase/include/arch0arch.h @@ -0,0 +1,1984 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/arch0arch.h + Common interface for redo log and dirty page archiver system + + *******************************************************/ + +#ifndef ARCH_ARCH_INCLUDE +#define ARCH_ARCH_INCLUDE + +#include +#include +#include "buf0buf.h" /* buf_page_t */ +#include "ut0mem.h" + +/** @name Archive file name prefix and constant length parameters. */ +/** @{ */ +/** Archive directory prefix */ +const char ARCH_DIR[] = OS_FILE_PREFIX "ib_archive"; + +/** Archive Log group directory prefix */ +const char ARCH_LOG_DIR[] = "log_group_"; + +/** Archive Page group directory prefix */ +const char ARCH_PAGE_DIR[] = "page_group_"; + +/** Archive log file prefix */ +const char ARCH_LOG_FILE[] = "ib_log_"; + +/** Archive page file prefix */ +const char ARCH_PAGE_FILE[] = "ib_page_"; + +/** TODO: Replace with more appropriate value based on log_sys.write_size */ +constexpr uint32_t OS_FILE_LOG_BLOCK_SIZE = 512; + +/** Minimum Archived file size */ +constexpr size_t LOG_FILE_MIN_SIZE = 64 * 1024; + +constexpr size_t LOG_FILE_MAX_NUM = 1000; + +/** @} */ + +/** File name for the durable file which indicates whether a group was made +durable or not. Required to differentiate durable group from group left over by +crash during clone operation. */ +constexpr char ARCH_PAGE_GROUP_DURABLE_FILE_NAME[] = "durable"; + +/** Byte length for printing LSN. +Each archive group name is appended with start LSN */ +const uint MAX_LSN_DECIMAL_DIGIT = 32; + +/** Max string length for archive log file name */ +const uint MAX_ARCH_LOG_FILE_NAME_LEN = + sizeof(ARCH_DIR) + 1 + sizeof(ARCH_LOG_DIR) + MAX_LSN_DECIMAL_DIGIT + 1 + + sizeof(ARCH_LOG_FILE) + MAX_LSN_DECIMAL_DIGIT + 1; + +/** Max string length for archive page file name */ +const uint MAX_ARCH_PAGE_FILE_NAME_LEN = + sizeof(ARCH_DIR) + 1 + sizeof(ARCH_PAGE_DIR) + MAX_LSN_DECIMAL_DIGIT + 1 + + sizeof(ARCH_PAGE_FILE) + MAX_LSN_DECIMAL_DIGIT + 1; + +/** Max string length for archive group directory name */ +const uint MAX_ARCH_DIR_NAME_LEN = + sizeof(ARCH_DIR) + 1 + sizeof(ARCH_PAGE_DIR) + MAX_LSN_DECIMAL_DIGIT + 1; + +/** Memory block size */ +constexpr uint ARCH_PAGE_BLK_SIZE = UNIV_PAGE_SIZE_DEF; + +/** Archiver client state. +Archiver clients request archiving for specific interval using +the start and stop interfaces. During this time the client is +attached to global Archiver system. A client copies archived +data for the interval after calling stop. System keeps the data +till the time client object is destroyed. + +@startuml + + state ARCH_CLIENT_STATE_INIT + state ARCH_CLIENT_STATE_STARTED + state ARCH_CLIENT_STATE_STOPPED + + [*] -down-> ARCH_CLIENT_STATE_INIT + ARCH_CLIENT_STATE_INIT -down-> ARCH_CLIENT_STATE_STARTED : Attach and start \ + archiving + ARCH_CLIENT_STATE_STARTED -right-> ARCH_CLIENT_STATE_STOPPED : Stop \ + archiving + ARCH_CLIENT_STATE_STOPPED -down-> [*] : Detach client + +@enduml */ +enum Arch_Client_State +{ + /** Client is initialized */ + ARCH_CLIENT_STATE_INIT = 0, + + /** Archiving started by client */ + ARCH_CLIENT_STATE_STARTED, + + /** Archiving stopped by client */ + ARCH_CLIENT_STATE_STOPPED +}; + +/** Archiver system state. +Archiver state changes are triggered by client request to start or +stop archiving and system wide events like shutdown fatal error etc. +Following diagram shows the state transfer. + +@startuml + + state ARCH_STATE_INIT + state ARCH_STATE_ACTIVE + state ARCH_STATE_PREPARE_IDLE + state ARCH_STATE_IDLE + state ARCH_STATE_ABORT + + [*] -down-> ARCH_STATE_INIT + + ARCH_STATE_INIT -down-> ARCH_STATE_ACTIVE : Start archiving + ARCH_STATE_ACTIVE -right-> ARCH_STATE_PREPARE_IDLE : Stop archiving + ARCH_STATE_PREPARE_IDLE -right-> ARCH_STATE_IDLE : All data archived + ARCH_STATE_IDLE -down-> ARCH_STATE_ABORT : Shutdown or Fatal Error + ARCH_STATE_PREPARE_IDLE --> ARCH_STATE_ACTIVE : Resume archiving + ARCH_STATE_IDLE --> ARCH_STATE_ACTIVE : Start archiving + ARCH_STATE_ABORT -down-> [*] + +@enduml */ +enum Arch_State +{ + /** Archiver is initialized */ + ARCH_STATE_INIT = 0, + + /** Archiver is active and archiving data */ + ARCH_STATE_ACTIVE, + + /** Archiver is processing last data chunks before idle state */ + ARCH_STATE_PREPARE_IDLE, + + /** Archiver is idle */ + ARCH_STATE_IDLE, + + /** Server is in read only mode, and hence the archiver */ + ARCH_STATE_READ_ONLY, + + /** Archiver is aborted */ + ARCH_STATE_ABORT +}; + +/** Archived data block state. +A data block is a block in memory that holds dirty page IDs before persisting +into disk. Shown below is the state transfer diagram for a data block. + +@startuml + + state ARCH_BLOCK_INIT + state ARCH_BLOCK_ACTIVE + state ARCH_BLOCK_READY_TO_FLUSH + state ARCH_BLOCK_FLUSHED + + [*] -down-> ARCH_BLOCK_INIT + ARCH_BLOCK_INIT -> ARCH_BLOCK_ACTIVE : Writing page ID + ARCH_BLOCK_ACTIVE -> ARCH_BLOCK_READY_TO_FLUSH : Block is full + ARCH_BLOCK_READY_TO_FLUSH -> ARCH_BLOCK_FLUSHED : Block is flushed + ARCH_BLOCK_FLUSHED --> ARCH_BLOCK_ACTIVE : Writing page ID + ARCH_BLOCK_FLUSHED -down-> [*] + +@enduml */ +enum Arch_Blk_State +{ + /** Data block is initialized */ + ARCH_BLOCK_INIT = 0, + + /** Data block is active and having data */ + ARCH_BLOCK_ACTIVE, + + /** Data block is full but not flushed to disk */ + ARCH_BLOCK_READY_TO_FLUSH, + + /** Data block is flushed and can be reused */ + ARCH_BLOCK_FLUSHED +}; + +/** Archiver block type */ +enum Arch_Blk_Type +{ + /* Block which holds reset information */ + ARCH_RESET_BLOCK = 0, + + /* Block which holds archived page IDs */ + ARCH_DATA_BLOCK +}; + +/** Archiver block flush type */ +enum Arch_Blk_Flush_Type +{ + /** Flush when block is full */ + ARCH_FLUSH_NORMAL = 0, + + /** Flush partial block. + Needed for persistent page tracking. */ + ARCH_FLUSH_PARTIAL +}; + +/** Page Archive doublewrite buffer block offsets */ +enum Arch_Page_Dblwr_Offset +{ + /** Archive doublewrite buffer page offset for RESET page. */ + ARCH_PAGE_DBLWR_RESET_PAGE = 0, + + /* Archive doublewrite buffer page offset for FULL FLUSH page. */ + ARCH_PAGE_DBLWR_FULL_FLUSH_PAGE, + + /* Archive doublewrite buffer page offset for PARTIAL FLUSH page. */ + ARCH_PAGE_DBLWR_PARTIAL_FLUSH_PAGE +}; + +/** Forward declarations */ +class Arch_Group; +class Arch_Log_Sys; +class Arch_Dblwr_Ctx; +class Arch_Recv_Group_Info; + +/** Guard to release resources safely */ +class Arch_scope_guard +{ + public: + /** Attach a function to the guard which releases some resource. */ + Arch_scope_guard(std::function function) { m_cleanup = function; } + + /** Release the resources automatically at the time of destruction. */ + ~Arch_scope_guard() + { + if (m_cleanup) + m_cleanup(); + } + + /** Manually release the resource. */ + void cleanup() + { + m_cleanup(); + m_cleanup = nullptr; + } + + private: + /** Function to release the resource. */ + std::function m_cleanup{}; +}; + +/** Position in page ID archiving system */ +struct Arch_Page_Pos +{ + /** Initialize a position */ + void init(); + + /** Position in the beginning of next block */ + void set_next(); + + /** Unique block number */ + uint32_t m_block_num; + + /** Offset within a block */ + uint m_offset; + + bool operator<(Arch_Page_Pos pos) + { + return (m_block_num < pos.m_block_num || + (m_block_num == pos.m_block_num && m_offset <= pos.m_offset)); + } +}; + +/** Structure which represents a point in a file. */ +struct Arch_Point +{ + /** LSN of the point */ + lsn_t lsn{LSN_MAX}; + + /** Position of the point */ + Arch_Page_Pos pos; +}; + +/* Structure which represents a file in a group and its reset points. */ +struct Arch_Reset_File +{ + /* Initialize the structure. */ + void init(); + + /* Index of the file in the group */ + uint m_file_index{0}; + + /* LSN of the first reset point in the vector of reset points this + structure maintains. Treated as the file LSN. */ + lsn_t m_lsn{LSN_MAX}; + + /* Vector of reset points which belong to this file */ + std::vector m_start_point; +}; + +/* Structure representing list of archived files. */ +using Arch_Reset = std::deque; + +/** In memory data block in Page ID archiving system */ +class Arch_Block +{ + public: + /** Constructor: Initialize elements + @param[in] blk_buf buffer for data block + @param[in] size buffer size + @param[in] type block type */ + Arch_Block(byte *blk_buf, uint size, Arch_Blk_Type type) + : m_data(blk_buf), m_size(size), m_type(type) {} + + /** Do a deep copy of the members of the block passed as the parameter. + @note This member needs to be updated whenever a new data member is added to + this class. */ + void copy_data(const Arch_Block *block); + + /** Set the block ready to begin writing page ID + @param[in] pos position to initiate block number */ + void begin_write(Arch_Page_Pos pos); + + /** End writing to a block. + Change state to #ARCH_BLOCK_READY_TO_FLUSH */ + void end_write(); + + /** Check if block is initialised or not. + @return true if it has been initialised, else false */ + bool is_init() const { return (m_state == ARCH_BLOCK_INIT); } + + bool is_active() const { return (m_state == ARCH_BLOCK_ACTIVE); } + /** Check if the block can be flushed or not. + @return true, if the block cannot be flushed */ + bool is_flushable() const { return (m_state != ARCH_BLOCK_READY_TO_FLUSH); } + + /** Set current block flushed. + Must hold page archiver sys operation mutex. */ + void set_flushed() { m_state = ARCH_BLOCK_FLUSHED; } + + /** Add page ID to current block + @param[in] page page from buffer pool + @param[in] pos Archiver current position + @return true, if successful + false, if no more space in current block */ + bool add_page(buf_page_t *page, Arch_Page_Pos *pos); + + /* Add reset information to the current reset block. + @param[in] reset_lsn reset lsn info + @param[in] reset_pos reset pos info which needs to be added + to the current reset block */ + void add_reset(lsn_t reset_lsn, Arch_Page_Pos reset_pos); + + /** Copy page Ids from this block at read position to a buffer. + @param[in] read_pos current read position + @param[in] read_len length of data to copy + @param[out] read_buff buffer to copy page IDs. + Caller must allocate the buffer. + @return true, if successful + false, if block is already overwritten */ + bool get_data(Arch_Page_Pos *read_pos, uint read_len, byte *read_buff); + + /** Copy page Ids from a buffer to this block. + @param[in] read_len length of data to copy + @param[in] read_buff buffer to copy page IDs from + @param[in] read_offset offset from where to write + @return true if successful */ + bool set_data(uint read_len, byte *read_buff, uint read_offset); + + /** Flush this block to the file group + @param[in] file_group current archive group + @param[in] type flush type + @return error code. */ + dberr_t flush(Arch_Group *file_group, Arch_Blk_Flush_Type type); + + /* Update the block header with the given LSN + @param[in] stop_lsn stop LSN to update in the block header + @param[in] reset_lsn reset LSN to update in the blk header */ + void update_block_header(lsn_t stop_lsn, lsn_t reset_lsn); + + /** @return data length of the block. */ + uint get_data_len() const { return m_data_len; } + + /** Set the data length of the block. + @param[in] data_len data length */ + void set_data_len(uint data_len) { m_data_len = data_len; } + + /** Set the reset length of the block. + @param[in] reset_lsn reset lsn */ + void set_reset_lsn(lsn_t reset_lsn) { m_reset_lsn = reset_lsn; } + + /** @return block number of the block. */ + uint64_t get_number() const { return (m_number); } + + /** @return stop lsn */ + lsn_t get_stop_lsn() const { return (m_stop_lsn); } + + /** Get oldest LSN among the pages that are added to this block + @return oldest LSN in block pages */ + lsn_t get_oldest_lsn() const { return (m_oldest_lsn); } + + /** Get current state of the block + @return block state */ + Arch_Blk_State get_state() const { return (m_state); } + + /** Check if the block data is valid. + @param[in] block block to be validated + @return true if it's a valid block, else false */ + static bool validate(byte *block); + + /** Get file index of the file the block belongs to. + @return file index */ + static uint get_file_index(uint64_t block_num, Arch_Blk_Type type); + + /** Checks if memory range is all zeros. + @param[in] start The pointer to first byte of a buffer + @param[in] number_of_bytes The number of bytes in the buffer + @return true if and only if `number_of_bytes` bytes pointed by + `start` are all zeros. */ + static bool is_zeros(const void *start, size_t number_of_bytes); + + /** Get block type from the block header. + @param[in] block block from where to get the type + @return block type */ + static Arch_Blk_Type get_type(byte *block); + + /** Get block data length from the block header. + @param[in] block block from where to get the data length + @return block data length */ + static uint get_data_len(byte *block); + + /** Get the stop lsn stored in the block header. + @param[in] block block from where to fetch the stop lsn + @return stop lsn */ + static lsn_t get_stop_lsn(byte *block); + + /** Get the block number from the block header. + @param[in] block block from where to fetch the block number + @return block number */ + static uint64_t get_block_number(byte *block); + + /** Get the reset lsn stored in the block header. + @param[in] block block from where to fetch the reset lsn + @return reset lsn */ + static lsn_t get_reset_lsn(byte *block); + + /** Get the checksum stored in the block header. + @param[in] block block from where to fetch the checksum + @return checksum */ + static uint32_t get_checksum(byte *block); + + /** Fetch the offset for a block in the archive file. + @param[in] block_num block number + @param[in] type type of block + @return file offset of the block */ + static uint64_t get_file_offset(uint64_t block_num, Arch_Blk_Type type); + + private: + /* @note member function copy_data needs to be updated whenever a new data + member is added to this class. */ + + /** Block data buffer */ + byte *m_data; + + /** Block data length in bytes */ + uint m_data_len{}; + + /** Total block size in bytes */ + uint m_size; + + /** State of the block. */ + Arch_Blk_State m_state{ARCH_BLOCK_INIT}; + + /** Unique block number */ + uint64_t m_number{}; + + /** Type of block. */ + Arch_Blk_Type m_type; + + /** Checkpoint lsn at the time the last page ID was added to the + block. */ + lsn_t m_stop_lsn{LSN_MAX}; + + /** Oldest LSN of all the page IDs added to the block since the last + checkpoint */ + lsn_t m_oldest_lsn{LSN_MAX}; + + /** Start LSN or the last reset LSN of the group */ + lsn_t m_reset_lsn{LSN_MAX}; +}; + +/** Archiver file context. +Represents a set of fixed size files within a group */ +class Arch_File_Ctx +{ + public: + class Recovery; + + /** Constructor: Initialize members */ + Arch_File_Ctx() { m_file.m_file = OS_FILE_CLOSED; } + + /** Destructor: Close open file and free resources */ + ~Arch_File_Ctx() + { + close(); + if (m_name_buf) + ut_free(m_name_buf); + } + + /** Initializes archiver file context. + @param[in] path path to the file + @param[in] base_dir directory name prefix + @param[in] base_file file name prefix + @param[in] num_files initial number of files + @return error code. */ + dberr_t init(const char *path, const char *base_dir, const char *base_file, + uint num_files); + + /** Open a file at specific index + @param[in] read_only open in read only mode + @param[in] start_lsn start lsn for the group + @param[in] file_index index of the file within the group which needs + to be opened + @param[in] file_offset start offset + @param[in] file_size maximum allowed file size or 0 to use its current + real size on disk as the limitation (file is full) + @return error code. */ + dberr_t open(bool read_only, lsn_t start_lsn, uint file_index, + uint64_t file_offset, uint64_t file_size); + + /** Add a new file and open + @param[in] start_lsn start lsn for the group + @param[in] new_file_size size limit for the new file + @param[in] initial_file_size initial size of file to create + @return error code. */ + dberr_t open_new(lsn_t start_lsn, uint64_t new_file_size, + uint64_t initial_file_size); + + /** Open next file for read + @param[in] start_lsn start lsn for the group + @param[in] file_offset start offset + @param[in] file_size maximum allowed file size or 0 to use its current + real size on disk as the limitation (file is full) + @return error code. */ + dberr_t open_next(lsn_t start_lsn, uint64_t file_offset, uint64_t file_size); + + /** Read data from the current file that is open. + Caller must ensure that the size is within the limits of current file + context. + @param[in,out] to_buffer read data into this buffer + @param[in] offset file offset from where to read + @param[in] size size of data to read in bytes + @return error code */ + dberr_t read(byte *to_buffer, const uint64_t offset, uint size); + + /** Resize file to provided size and overwrite the whole file with 0x00. + @param[in] file_size new file size + @return error code */ + dberr_t resize_and_overwrite_with_zeros(uint64_t file_size); + + /** Write data to this file context from the given file offset. + Data source is another file context or buffer. If buffer is NULL, data is + copied from input file context. Caller must ensure that the size is within + the limits of current file for both source and destination file context. + @param[in] from_file file context to copy data from + @param[in] from_buffer buffer to copy data or NULL + @param[in] offset file offset from where to write + @param[in] size size of data to copy in bytes + @return error code */ + dberr_t write(Arch_File_Ctx *from_file, byte *from_buffer, uint offset, + uint size); + + /** Write data to this file context from the current offset. + Data source is another file context or buffer. If buffer is NULL, data is + copied from input file context. Caller must ensure that the size is within + the limits of current file for both source and destination file context. + @param[in] from_file file context to copy data from + @param[in] from_buffer buffer to copy data or NULL + @param[in] size size of data to copy in bytes + @return error code */ + dberr_t write(Arch_File_Ctx *from_file, byte *from_buffer, uint size); + + /** Flush file. */ + void flush() + { + if (m_file.m_file != OS_FILE_CLOSED) + os_file_flush(m_file); + } + + /** Close file, if open */ + void close() + { + if (m_file.m_file != OS_FILE_CLOSED) + { + os_file_close(m_file); + m_file.m_file= OS_FILE_CLOSED; + } + } + + /** Check if file is closed + @return true, if file is closed */ + bool is_closed() const { return (m_file.m_file == OS_FILE_CLOSED); } + + /** Check how much is left in current file + @return length left in bytes */ + uint64_t bytes_left() const + { + ut_ad(m_size >= m_offset); + return (m_size - m_offset); + } + + /** Construct file name at specific index + @param[in] idx file index + @param[in] dir_lsn lsn of the group + @param[out] buffer file name including path. + The buffer is allocated by caller. + @param[in] length buffer length */ + void build_name(uint idx, lsn_t dir_lsn, char *buffer, uint length); + + /** Construct group directory name + @param[in] dir_lsn lsn of the group + @param[out] buffer directory name. + The buffer is allocated by caller. + @param[in] length buffer length */ + void build_dir_name(lsn_t dir_lsn, char *buffer, uint length); + + /** Get the logical size of a file. + @return logical file size. */ + uint64_t get_size() const { return (m_size); } + + /* Fetch offset of the file open in this context. + @return file offset */ + uint64_t get_offset() const { return (m_offset); } + + /** Get current file index. + @return current file index */ + uint get_index() const { return m_index; } + + /** Get number of files + @return current file count */ + uint get_count() const { return (m_count); } + + /** Get the physical size of a file that is open in this context. + @return physical file size */ + uint64_t get_phy_size() const + { + ut_ad(m_name_buf != nullptr); + os_file_size_t file_size = os_file_get_size(m_name_buf); + return (file_size.m_total_size); + } + + /** Update stop lsn of a file in the group. + @param[in] file_index file_index the current write_pos belongs to + @param[in] stop_lsn stop point */ + void update_stop_point(uint file_index, lsn_t stop_lsn); + +#ifdef UNIV_DEBUG + /** Check if the information maintained in the memory is the same + as the information maintained in the files. + @return true if both sets of information are the same + @param[in] group group whose file is being validated + @param[in] file_index index of the file which is being validated + @param[in] start_lsn start LSN + @param[in,out] reset_count count of files which has been validated + @return true if both the sets of information are the same. */ + bool validate(Arch_Group *group, uint file_index, lsn_t start_lsn, + uint &reset_count); +#endif + + /** Update the reset information in the in-memory structure that we maintain + for faster access. + @param[in] lsn lsn at the time of reset + @param[in] pos pos at the time of reset */ + void save_reset_point_in_mem(lsn_t lsn, Arch_Page_Pos pos); + + /** Find the appropriate reset LSN that is less than or equal to the + given lsn and fetch the reset point. + @param[in] check_lsn LSN to be searched against + @param[out] reset_point reset position of the fetched reset point + @return true if the search was successful. */ + bool find_reset_point(lsn_t check_lsn, Arch_Point &reset_point); + + /** Find the first stop LSN that is greater than the given LSN and fetch + the stop point. + @param[in] group the group whose stop_point we're interested in + @param[in] check_lsn LSN to be searched against + @param[out] stop_point stop point + @param[in] last_pos position of the last block in the group; + m_write_pos if group is active and m_stop_pos if not + @return true if the search was successful. */ + bool find_stop_point(Arch_Group *group, lsn_t check_lsn, + Arch_Point &stop_point, Arch_Page_Pos last_pos); + + /** Delete a single file belonging to the specified file index. + @param[in] file_index file index of the file which needs to be deleted + @param[in] begin_lsn group's start lsn + @return true if successful, else false. */ + bool delete_file(uint file_index, lsn_t begin_lsn); + + /** Delete all files for this archive group + @param[in] begin_lsn group's start lsn */ + void delete_files(lsn_t begin_lsn); + + /** Purge archived files until the specified purge LSN. + @param[in] begin_lsn start LSN of the group + @param[in] end_lsn end LSN of the group + @param[in] purge_lsn purge LSN until which files needs to be purged + @return LSN until which purging was successful + @retval LSN_MAX if there was no purging done. */ + lsn_t purge(lsn_t begin_lsn, lsn_t end_lsn, lsn_t purge_lsn); + + /** Fetch the status of the page tracking system. + @param[out] status vector of a pair of (ID, bool) where ID is the + start/stop point and bool is true if the ID is a start point else false */ + void get_status(std::vector> &status) + { + for (auto reset_file : m_reset) + { + for (auto reset_point : reset_file.m_start_point) + status.push_back(std::make_pair(reset_point.lsn, true)); + } + } + + private: +#ifdef UNIV_DEBUG + /** Check if the reset information maintained in the memory is the same + as the information maintained in the given file. + @param[in] file file descriptor + @param[in] file_index index of the file + @param[in,out] reset_count number of files processed containing + reset data + @return true if both sets of information are the same */ + bool validate_reset_block_in_file(pfs_os_file_t file, uint file_index, + uint &reset_count); + + /** Check if the stop LSN maintained in the memory is the same as the + information maintained in the files. + @param[in] group group whose file is being validated + @param[in] file file descriptor + @param[in] file_index index of the file for which the validation is + happening + @return true if both the sets of information are the same. */ + bool validate_stop_point_in_file(Arch_Group *group, pfs_os_file_t file, + uint file_index); +#endif + + /** Fetch reset lsn of a particular reset point pertaining to a file. + @param[in] block_num block number where the reset occurred. + @return reset lsn */ + lsn_t fetch_reset_lsn(uint64_t block_num); + + private: + /** File name buffer. + Used if caller doesn't allocate buffer. */ + char *m_name_buf{nullptr}; + + /** File name buffer length */ + uint m_name_len{}; + + /** Fixed length part of the file. + Path ended with directory separator. */ + uint m_base_len{}; + + /** Fixed part of the path to file */ + const char *m_path_name{nullptr}; + + /** Directory name prefix */ + const char *m_dir_name{nullptr}; + + /** File name prefix */ + const char *m_file_name{nullptr}; + + /** Current file descriptor */ + pfs_os_file_t m_file; + + /** File index within the archive group */ + uint m_index{}; + + /** Current number of files in the archive group */ + uint m_count{}; + + /** Current file offset */ + uint64_t m_offset{}; + + /** File size limit in bytes */ + uint64_t m_size{}; + + /** Queue of file structure holding reset information pertaining to + their respective files in a group. + Protected by Arch_Page_Sys::m_mutex and Arch_Page_Sys::m_oper_mutex. + @note used only by the page archiver */ + Arch_Reset m_reset; + + /** Vector of stop points corresponding to a file. + Stop point refers to the stop lsn (checkpoint lsn) until which the pages are + guaranteed to be tracked in a file. Each block in a file maintains this + information. + Protected by Arch_Page_Sys::m_oper_mutex. + @note used only by the page archiver */ + std::vector m_stop_points; +}; + +/** Number which tries to uniquely identify the archived data (unless it is +zero, which stands for unsupported identification). Currently only redo log +files are identified (by Log_uuid's value). */ +typedef uint32_t Arch_group_uuid; + +/** Contiguous archived data for redo log or page tracking. +If there is a gap, that is if archiving is stopped and started, a new +group is created. */ +class Arch_Group +{ + public: + /** Constructor: Initialize members + @param[in] first_lsn aligned start LSN for the group + @param[in] start_lsn start LSN for the group + @param[in] header_len length of header for archived files + @param[in] mutex archive system mutex from caller */ + Arch_Group(lsn_t first_lsn, lsn_t start_lsn, uint header_len, + mysql_mutex_t *mutex) + : m_first_lsn(first_lsn), m_begin_lsn(start_lsn), + m_header_len(header_len) +#ifdef UNIV_DEBUG + , m_arch_mutex(mutex) +#endif /* UNIV_DEBUG */ + { + ut_ad(first_lsn <= start_lsn); + m_active_file.m_file= OS_FILE_CLOSED; + m_durable_file.m_file= OS_FILE_CLOSED; + m_stop_pos.init(); + } + + /** Destructor: Delete all files for non-durable archiving. */ + ~Arch_Group(); + + /** Initialize the doublewrite buffer file context for the archive group. + @param[in] path path to the file + @param[in] base_file file name prefix + @param[in] num_files initial number of files + @param[in] file_size file size in bytes + @return error code. */ + static dberr_t init_dblwr_file_ctx(const char *path, const char *base_file, + uint num_files, uint64_t file_size); + + /** Initialize the file context for the archive group. + File context keeps the archived data in files on disk. There + is one file context for a archive group. + @param[in] path path to the file + @param[in] base_dir directory name prefix + @param[in] base_file file name prefix + @param[in] num_files initial number of files + @param[in] file_size size of file used when a new file is created + @param[in] uuid uuid of this arch group or 0 if unknown + @return error code. */ + dberr_t init_file_ctx(const char *path, const char *base_dir, + const char *base_file, uint num_files, + uint64_t file_size, Arch_group_uuid uuid) + { + m_uuid= uuid; + m_file_size= file_size; + return (m_file_ctx.init(path, base_dir, base_file, num_files)); + } + + /* Close the file contexts when they're not required anymore. */ + void close_file_ctxs() + { + m_file_ctx.close(); + + if (m_durable_file.m_file != OS_FILE_CLOSED) + { + os_file_close(m_durable_file); + m_durable_file.m_file = OS_FILE_CLOSED; + } + } + + /** Mark archive group inactive. + A group is marked inactive by archiver background before entering + into idle state ARCH_STATE_IDLE. + @param[in] end_lsn lsn where redo archiving is stopped */ + void disable(lsn_t end_lsn) { + m_is_active= false; + + if (end_lsn != LSN_MAX) + m_end_lsn= end_lsn; + } + + /** Attach a client to the archive group. + @param[in] is_durable true, if durable tracking is requested */ + void attach(bool is_durable) + { + mysql_mutex_assert_owner(m_arch_mutex); + ++m_num_active; + + if (is_durable) + ++m_dur_ref_count; + else + { + ut_ad(m_ref_count < std::numeric_limits::max()); + if (m_ref_count < std::numeric_limits::max()) + ++m_ref_count; + } + } + + /** Detach a client when archiving is stopped by the client. + The client still has reference to the group so that the group + is not destroyed when it retrieves the archived data. The + reference is removed later by #Arch_Group::release. + @param[in] stop_lsn archive stop lsn for client + @param[in] stop_pos archive stop position for client. Used only by + the page_archiver. + @return number of active clients */ + uint detach(lsn_t stop_lsn, Arch_Page_Pos *stop_pos) + { + ut_ad(m_num_active > 0); + mysql_mutex_assert_owner(m_arch_mutex); + --m_num_active; + + if (m_num_active == 0) + { + m_end_lsn = stop_lsn; + if (stop_pos != nullptr) + m_stop_pos = *stop_pos; + } + return m_num_active; + } + + /** Release the archive group from a client. + Reduce the reference count. When all clients release the group, + the reference count falls down to zero. The function would then + return zero and the caller can remove the group. + @param[in] is_durable the client needs durable archiving */ + void release(bool is_durable) + { + mysql_mutex_assert_owner(m_arch_mutex); + ut_ad(!is_durable); + if (is_durable) + /* For durable, m_ref_count was not incremented. */ + return; + + ut_ad(m_ref_count > 0); + --m_ref_count; + /* If there was a bug, and m_ref_count was 0 before the decrement, + it would become std::numeric_limits::max(), + and the caller would not remove the group. If we called attach() + afterwards (holding still the m_arch_mutex), the m_ref_count would + stay unchanged, because there is mechanism protecting from overflows. + This way, the scope of the potential bug, is limited to the group not + being removed. */ + } + + /** Construct file name for the active file which indicates whether a group + is active or not. + @note Used only by the page archiver. + @return error code. */ + dberr_t build_active_file_name(); + + /** Construct file name for the durable file which indicates whether a group + was made durable or not. + @note Used only by the page archiver. + @return error code. */ + dberr_t build_durable_file_name(); + + /** Mark the group active by creating a file in the respective group + directory. This is required at the time of recovery to know whether a group + was active or not in case of a crash. + @note Used only by the page archiver. + @return error code. */ + int mark_active(); + + /** Mark the group durable by creating a file in the respective group + directory. This is required at the time of recovery to differentiate durable + group from group left over by crash during clone operation. + @note Used only by the page archiver. + @return error code. */ + int mark_durable(); + + /** Mark the group inactive by deleting the 'active' file. This is required + at the time of crash recovery to know whether a group was active or not in + case of a crash. + @note Used only by the page archiver. + @return error code */ + int mark_inactive(); + + /** Check if archiving is going on for this group + @return true, if the group is active */ + bool is_active() const { return (m_is_active); } + + /** Write the header (RESET page) to an archived file. + @note Used only by the Page Archiver and not by the Redo Log Archiver. + @param[in] from_buffer buffer to copy data + @param[in] length size of data to copy in bytes + @note Used only by the Page Archiver. + @return error code */ + dberr_t write_file_header(byte *from_buffer, uint length); + + /** Write to the doublewrite buffer before writing archived data to a file. + The source is either a file context or buffer. Caller must ensure that data + is in single file in source file context. + @param[in] from_file file context to copy data from + @param[in] from_buffer buffer to copy data or NULL + @param[in] write_size size of data to write in bytes + @param[in] offset offset from where to write + @note Used only by the Page Archiver. + @return error code */ + static dberr_t write_to_doublewrite_file(Arch_File_Ctx *from_file, + byte *from_buffer, uint write_size, + Arch_Page_Dblwr_Offset offset); + + /** Archive data to one or more files. + The source is either a file context or buffer. Caller must ensure that data + is in single file in source file context. + @param[in] from_file file context to copy data from + @param[in] from_buffer buffer to copy data or NULL + @param[in] length size of data to copy in bytes + @param[in] partial_write true if the operation is part of partial flush + @param[in] do_persist doublewrite to ensure persistence + @return error code */ + dberr_t write_to_file(Arch_File_Ctx *from_file, byte *from_buffer, + uint length, bool partial_write, bool do_persist); + + /** Find the appropriate reset LSN that is less than or equal to the + given lsn and fetch the reset point. + @param[in] check_lsn LSN to be searched against + @param[out] reset_point reset position of the fetched reset point + @return true if the search was successful. */ + bool find_reset_point(lsn_t check_lsn, Arch_Point &reset_point) + { + return (m_file_ctx.find_reset_point(check_lsn, reset_point)); + } + + /** Find the first stop LSN that is greater than the given LSN and fetch + the stop point. + @param[in] check_lsn LSN to be searched against + @param[out] stop_point stop point + @param[in] write_pos latest write_pos + @return true if the search was successful. */ + bool find_stop_point(lsn_t check_lsn, Arch_Point &stop_point, + Arch_Page_Pos write_pos) + { + ut_ad(validate_info_in_files()); + Arch_Page_Pos last_pos= is_active() ? write_pos : m_stop_pos; + return (m_file_ctx.find_stop_point(this, check_lsn, stop_point, last_pos)); + } + +#ifdef UNIV_DEBUG + /** Adjust end LSN to end of file. This is used in debug + mode to test the case when LSN is at file boundary. + @param[in,out] stop_lsn stop lsn for client + @param[out] blk_len last block length */ + void adjust_end_lsn(lsn_t &stop_lsn, uint32_t &blk_len); + + /** Adjust redo copy length to end of file. This is used + in debug mode to archive only till end of file. + @param[in] arch_lsn LSN up to which data is already archived + @param[in,out] copy_len length of data to copy in bytes */ + void adjust_copy_length(lsn_t arch_lsn, uint32_t ©_len); + + /** Check if the information maintained in the memory is the same + as the information maintained in the files. + @return true if both sets of information are the same */ + bool validate_info_in_files(); +#endif /* UNIV_DEBUG */ + + /** Get the total number of archived files belonging to this group. + @return number of archived files */ + uint get_file_count() const { return (m_file_ctx.get_count()); } + + /** Check if any client (durable or not) is attached to the archiver. + @return true if any client is attached, else false */ + bool is_referenced() const + { + return (m_ref_count > 0) || (m_dur_ref_count > 0); + } + + /** Check if any client requiring durable archiving is active. + @return true if any durable client is still attached, else false */ + bool is_durable_client_active() const + { + return (m_num_active != m_ref_count); + } + + /** Check if any client requires durable archiving. + @return true if there is at least 1 client that requires durable archiving*/ + bool is_durable() const { return (m_dur_ref_count > 0); } + + /** Purge archived files until the specified purge LSN. + @param[in] purge_lsn LSN until which archived files needs to be + purged + @param[out] purged_lsn LSN until which purging is successful; + LSN_MAX if there was no purging done + @return error code */ + uint purge(lsn_t purge_lsn, lsn_t &purged_lsn); + + /** Operations to be done at the time of shutdown. */ + static void shutdown() { s_dblwr_file_ctx.close(); } + + /** Update the reset information in the in-memory structure that we maintain + for faster access. + @param[in] lsn lsn at the time of reset + @param[in] pos pos at the time of reset */ + void save_reset_point_in_mem(lsn_t lsn, Arch_Page_Pos pos) + { + m_file_ctx.save_reset_point_in_mem(lsn, pos); + } + + /** Update stop lsn of a file in the group. + @param[in] pos stop position + @param[in] stop_lsn stop point */ + void update_stop_point(Arch_Page_Pos pos, lsn_t stop_lsn) + { + m_file_ctx.update_stop_point( + Arch_Block::get_file_index(pos.m_block_num, ARCH_DATA_BLOCK), stop_lsn); + } + + /** Recover the information belonging to this group from the archived files. + @param[in,out] group_info structure containing information of a + group obtained during recovery by scanning files + @param[in] dblwr_ctx file context related to doublewrite buffer + @return error code */ + dberr_t recover(Arch_Recv_Group_Info &group_info, Arch_Dblwr_Ctx *dblwr_ctx); + + /** Parse block for block info (header/data). + @param[in] cur_pos position to read + @param[in,out] buff buffer into which to write the parsed data + @param[in] buff_len length of the buffer + @return error code */ + int read_data(Arch_Page_Pos cur_pos, byte *buff, uint buff_len); + + /** Get archived file name at specific index in this group. + Caller would use it to open and copy data from archived files. + @param[in] idx file index in the group + @param[out] name_buf file name and path. Caller must + allocate the buffer. + @param[in] buf_len allocated buffer length */ + void get_file_name(uint idx, char *name_buf, uint buf_len) + { + ut_ad(name_buf != nullptr); + + /* Build name from the file context. */ + m_file_ctx.build_name(idx, m_begin_lsn, name_buf, buf_len); + } + + /** Get the current file size for this group. + Fixed size files are used for archiving data in a group. + @return file size in bytes */ + uint64_t get_file_size() const { return m_file_size; } + + /** Get aligned start LSN for this group + @return aligned start LSN */ + lsn_t get_first_lsn() const { return (m_first_lsn); } + + /** Get start LSN for this group + @return start LSN */ + lsn_t get_begin_lsn() const { return (m_begin_lsn); } + + /** @return stop LSN for this group */ + lsn_t get_end_lsn() const { return (m_end_lsn); } + + /** @return stop block position of the group. */ + Arch_Page_Pos get_stop_pos() const { return (m_stop_pos); } + + /** @return uuid for the arch group */ + Arch_group_uuid get_uuid() const { return m_uuid; } + + /** Fetch the status of the page tracking system. + @param[out] status vector of a pair of (ID, bool) where ID is the + start/stop point and bool is true if the ID is a start point else false */ + void get_status(std::vector> &status) + { + m_file_ctx.get_status(status); + + if (!is_active()) + status.push_back(std::make_pair(m_end_lsn, false)); + } + + /** Open the file which was open at the time of a crash, during crash + recovery, and set the file offset to the last written offset. + @param[in] write_pos latest write position at the time of crash/shutdown + @param[in] create_new create new file if file not present + @return error code. */ + dberr_t open_file(Arch_Page_Pos write_pos, bool create_new); + + /** Align down LSN value to multiples of OS_FILE_LOG_BLOCK_SIZE with respect + to a reference LSN value. + @param[in] lsn LSN value to align + @param[in] ref_lsn reference LSN + @return aligned LSN value. */ + static lsn_t align_lsn(lsn_t lsn, lsn_t ref_lsn) + { + lsn_t lsn_diff = (ref_lsn <= lsn) ? (lsn - ref_lsn) : + (ref_lsn - lsn + OS_FILE_LOG_BLOCK_SIZE); + lsn_diff = ut_uint64_align_down(lsn_diff, OS_FILE_LOG_BLOCK_SIZE); + lsn_t r = (ref_lsn <= lsn) ? (ref_lsn + lsn_diff) : (ref_lsn - lsn_diff); + ut_ad(r <= lsn); + return r; + } + + /** Align down LSN value to multiples of OS_FILE_LOG_BLOCK_SIZE with respect + to groups first LSN value. + @param[in] lsn LSN value to align + @return aligned LSN value. */ + lsn_t align_lsn(lsn_t lsn) const + { + ut_ad(m_first_lsn <= lsn); + return Arch_Group::align_lsn(lsn, m_first_lsn); + } + + /** Disable copy construction */ + Arch_Group(Arch_Group const &) = delete; + + /** Disable assignment */ + Arch_Group &operator=(Arch_Group const &) = delete; + + private: + class Recovery; + + /** Get page IDs from archived file + @param[in] read_pos position to read from + @param[in] read_len length of data to read + @param[in] read_buff buffer to read page IDs + @return error code */ + int read_from_file(Arch_Page_Pos *read_pos, uint read_len, byte *read_buff); + + /** Get the directory name for this archive group. + It is used for cleaning up the archive directory. + @param[out] name_buf directory name and path. Caller must + allocate the buffer. + @param[in] buf_len buffer length */ + void get_dir_name(char *name_buf, uint buf_len) + { + m_file_ctx.build_dir_name(m_begin_lsn, name_buf, buf_len); + } + + private: + /** If the group is active */ + bool m_is_active{true}; + + /** To know which group was active at the time of a crash/shutdown during + recovery we create an empty file in the group directory. This holds the name + of the file. */ + char *m_active_file_name{nullptr}; + + /** File descriptor for a file required to indicate that the group was + active at the time of crash during recovery . */ + pfs_os_file_t m_active_file; + + /** File name for the durable file which indicates whether a group was made + durable or not. Required to differentiate durable group from group left over + by crash during clone operation. */ + char *m_durable_file_name{nullptr}; + + /** File descriptor for a file to indicate that the group was made durable or + not. Required to differentiate durable group from group left over by crash + during clone operation. */ + pfs_os_file_t m_durable_file; + + /** Number of clients referencing the group */ + uint m_ref_count{}; + + /** Number of clients referencing for durable archiving */ + uint m_dur_ref_count{}; + + /** Number of clients for which archiving is in progress */ + uint m_num_active{}; + + /** Start LSN aligned with log_sys.first_lsn in multiples of + OS_FILE_LOG_BLOCK_SIZE. Archived logs for the group starts at + this LSN. We don't currently support concurrent redo log resize + which could change the alignment of first_lsn. */ + lsn_t m_first_lsn{LSN_MAX}; + + /** Desired start LSN for the archive group. This is typically a + checkpoint LSN. */ + lsn_t m_begin_lsn{LSN_MAX}; + + /** End lsn for this archive group */ + lsn_t m_end_lsn{LSN_MAX}; + + /** Stop position of the group, if it's not active. */ + Arch_Page_Pos m_stop_pos{}; + + /** Header length for the archived files */ + uint m_header_len{}; + + /** Size of file used when a new file is being created. */ + uint64_t m_file_size; + + /** UUID generated for this arch group. */ + Arch_group_uuid m_uuid{}; + + /** Archive file context */ + Arch_File_Ctx m_file_ctx; + + /** Doublewrite buffer file context. + Note - Used only in the case of page archiver. */ + static Arch_File_Ctx s_dblwr_file_ctx; + +#ifdef UNIV_DEBUG + /** Mutex protecting concurrent operations by multiple clients. + This is either the redo log or page archive system mutex. Currently + used for assert checks. */ + mysql_mutex_t *m_arch_mutex; +#endif /* UNIV_DEBUG */ +}; + +/** A list of archive groups */ +using Arch_Grp_List = std::list>; + +/** An iterator for archive group */ +using Arch_Grp_List_Iter = Arch_Grp_List::iterator; + +/** Redo log archiving system */ +class Arch_Log_Sys +{ + public: + /** Constructor: Initialize members */ + Arch_Log_Sys() + : m_state(ARCH_STATE_INIT), + m_archived_lsn(LSN_MAX), + m_group_list(), + m_current_group() + { + mysql_mutex_init(0, &m_mutex, nullptr); + } + + /** Destructor: Free mutex */ + ~Arch_Log_Sys() + { + ut_ad(m_state == ARCH_STATE_INIT || m_state == ARCH_STATE_ABORT); + ut_ad(m_current_group == nullptr); + ut_ad(m_group_list.empty()); + + mysql_mutex_destroy(&m_mutex); + } + + /** Check if archiving is in progress. + In #ARCH_STATE_PREPARE_IDLE state, all clients have already detached + but archiver background task is yet to finish. + @return true, if archiving is active */ + bool is_active() const + { + return (m_state == ARCH_STATE_ACTIVE || + m_state == ARCH_STATE_PREPARE_IDLE); + } + + /** Check if archiver system is in initial state + @return true, if redo log archiver state is #ARCH_STATE_INIT */ + bool is_init() const { return (m_state == ARCH_STATE_INIT); } + + /** Get LSN up to which redo is archived + @return last archived redo LSN */ + lsn_t get_archived_lsn() const + { + return m_archived_lsn.load(); + } + + /** Get recommended archived redo file size + @return size of file in bytes */ + os_offset_t get_recommended_file_size() const; + + /** Get current redo log archive group + @return current archive group */ + Arch_Group *get_arch_group() { return (m_current_group); } + + /** Start redo log archiving. + If archiving is already in progress, the client + is attached to current group. + @param[out] group log archive group + @param[out] start_lsn start lsn for client + @param[out] header redo log header + @param[in] is_durable if client needs durable archiving + @return error code */ + int start(Arch_Group *&group, lsn_t &start_lsn, byte *header, + bool is_durable); + + /** Stop redo log archiving. + If other clients are there, the client is detached from + the current group. + @param[out] group log archive group + @param[out] stop_lsn stop lsn for client + @param[out] log_blk redo log trailer block + @param[in,out] blk_len length in bytes + @return error code */ + int stop(Arch_Group *group, lsn_t &stop_lsn, byte *log_blk, + uint32_t &blk_len); + + /** Force to abort the archiver (state becomes ARCH_STATE_IDLE or + ARCH_STATE_ABORT). */ + void force_abort(); + + /** Update archiver log system state under log_sys.latch.rd_lock. Caller is + expected to hold log archiver mutex. + @param[in] state state to assign to m_state */ + void update_state(Arch_State state); + + /** Waits until the archiver has archived enough for log_writer to proceed + or until the archiver becomes aborted. Caller must hold the log_sys.latch. + @param[in] next_write_lsn LSN up to which the caller is going to write. */ + void wait_archiver(lsn_t next_write_lsn); + + /** Release the current group from client. + @param[in] group group the client is attached to + @param[in] is_durable if client needs durable archiving */ + void release(Arch_Group *group, bool is_durable); + + /** Archive accumulated redo log in current group. + This interface is for archiver background task to archive redo log + data by calling it repeatedly over time. + @param[in, out] init true when called the first time; it will + then be set to false + @param[in] curr_ctx system redo logs to copy data from + @param[out] arch_lsn LSN up to which archiving is completed + @param[out] wait true, if no more redo to archive + @return true, if archiving is aborted */ + bool archive(bool init, Arch_File_Ctx *curr_ctx, lsn_t *arch_lsn, bool *wait); + + /** Acquire redo log archiver mutex. + It synchronizes concurrent start and stop operations by + multiple clients. */ + void arch_mutex_enter() { mysql_mutex_lock(&m_mutex); } + + /** Release redo log archiver mutex */ + void arch_mutex_exit() { mysql_mutex_unlock(&m_mutex); } + + /** Disable copy construction */ + Arch_Log_Sys(Arch_Log_Sys const &) = delete; + + /** Disable assignment */ + Arch_Log_Sys &operator=(Arch_Log_Sys const &) = delete; + + private: + /** Wait for archive system to come out of #ARCH_STATE_PREPARE_IDLE. + If the system is preparing to idle, #start needs to wait + for it to come to idle state. + @return true, if successful + false, if needs to abort */ + bool wait_idle(); + + /** Wait for redo log archive up to the target LSN. + We need to wait till current log sys LSN during archive stop. + @param[in] target_lsn target archive LSN to wait for + @return error code */ + int wait_archive_complete(lsn_t target_lsn); + + /** Update checkpoint LSN and related information in redo + log header block. + @param[in,out] header redo log header buffer + @param[in] first_lsn first LSN of the archived log + @param[in] checkpoint_lsn LSN of the checkpoint + @param[in] end_lsn LSN of the checkpoint record */ + void update_header(byte *header, lsn_t first_lsn, lsn_t checkpoint_lsn, + lsn_t end_lsn); + + /** Check and set log archive system state and output the + amount of redo log available for archiving. + @param[in] is_abort need to abort + @param[in,out] archived_lsn LSN up to which redo log is archived + @param[out] to_archive amount of redo log to be archived */ + Arch_State check_set_state(bool is_abort, lsn_t *archived_lsn, + uint *to_archive); + + /** Copy redo log from file context to archiver files. + @param[in] file_ctx file context for system redo logs + @param[in] length data to copy in bytes + @return error code */ + dberr_t copy_log(Arch_File_Ctx *file_ctx, uint length); + + private: + /** Mutex to protect concurrent start, stop operations */ + mysql_mutex_t m_mutex; + + /** Archiver system state. + #m_state is protected by #m_mutex and #log_t::writer_mutex. For changing + the state both needs to be acquired. For reading, hold any of the two + mutexes. Same is true for #m_archived_lsn. */ + Arch_State m_state; + + /** System has archived log up to this LSN */ + std::atomic m_archived_lsn; + + /** List of log archive groups */ + Arch_Grp_List m_group_list; + + /** Current archive group */ + Arch_Group *m_current_group; + + /** Chunk size to copy redo data */ + uint m_chunk_size; + + /** System log file number where the archiving started */ + uint m_start_log_index; + + /** System log file offset where the archiving started */ + uint64_t m_start_log_offset; +}; + +/** Vector of page archive in memory blocks */ +using Arch_Block_Vec = std::vector>; + +/** Page archiver in memory data */ +struct ArchPageData +{ + /** Constructor */ + ArchPageData() = default; + + /** Allocate buffer and initialize blocks + @return true, if successful */ + bool init(); + + /** Delete blocks and buffer */ + void clean(); + + /** Get the block for a position + @param[in] pos position in page archive sys + @param[in] type block type + @return page archive in memory block */ + Arch_Block *get_block(Arch_Page_Pos *pos, Arch_Blk_Type type); + + /** @return temporary block used to copy active block for partial flush. */ + Arch_Block *get_partial_flush_block() const + { + return (m_partial_flush_block); + } + + /** Vector of data blocks */ + Arch_Block_Vec m_data_blocks{}; + + /** Reset block */ + Arch_Block *m_reset_block{nullptr}; + + /** Temporary block used to copy active block for partial flush. */ + Arch_Block *m_partial_flush_block{nullptr}; + + /** Block size in bytes */ + uint m_block_size{}; + + /** Total number of blocks */ + uint m_num_data_blocks{}; + + /** In memory buffer */ + byte *m_buffer{nullptr}; +}; + +/** Forward declaration. */ +class Page_Arch_Client_Ctx; + +/** Dirty page archive system */ +class Arch_Page_Sys +{ + public: + /** Constructor: Initialize elements and create mutex */ + Arch_Page_Sys(); + + /** Destructor: Free memory buffer and mutexes */ + ~Arch_Page_Sys(); + + /** Start dirty page ID archiving. + If archiving is already in progress, the client is attached to current group. + @param[out] group page archive group the client gets attached to + @param[out] start_lsn start lsn for client in archived data + @param[out] start_pos start position for client in archived data + @param[in] is_durable true if client needs durable archiving + @param[in] restart true if client is already attached to current group + @param[in] recovery true if archiving is being started during + recovery + @return error code */ + int start(Arch_Group **group, lsn_t *start_lsn, Arch_Page_Pos *start_pos, + bool is_durable, bool restart, bool recovery); + + /** Stop dirty page ID archiving. + If other clients are there, the client is detached from the current group. + @param[in] group page archive group the client is attached to + @param[out] stop_lsn stop lsn for client + @param[out] stop_pos stop position in archived data + @param[in] is_durable true if client needs durable archiving + @return error code */ + int stop(Arch_Group *group, lsn_t *stop_lsn, Arch_Page_Pos *stop_pos, + bool is_durable); + + /** Start dirty page ID archiving during recovery. + @param[in,out] info information related to a group required for recovery + @return error code */ + int recovery_load_and_start(const Arch_Recv_Group_Info &info); + + /** Release the current group from client. + @param[in] group group the client is attached to + @param[in] is_durable if client needs durable archiving + @param[in] start_pos start position when the client calling the + release was started */ + void release(Arch_Group *group, bool is_durable, Arch_Page_Pos start_pos); + + /** Check and add page ID to archived data. + Check for duplicate page. + @param[in] bpage page to track + @param[in] track_lsn LSN when tracking started + @param[in] oldest_lsn oldest LSN of the page + @param[in] track_mark if the page is marked for tracking */ + void track_page(buf_page_t *bpage, lsn_t track_lsn, lsn_t oldest_lsn, + bool track_mark); + + /** Flush all the unflushed inactive blocks and flush the active block if + required. + @note Used only during the checkpointing process. + @param[in] checkpoint_lsn next checkpoint LSN */ + void flush_at_checkpoint(lsn_t checkpoint_lsn); + + /** Archive dirty page IDs in current group. + This interface is for archiver background task to flush page archive + data to disk by calling it repeatedly over time. + @param[out] wait true, if no more data to archive + @return true, if archiving is aborted */ + bool archive(bool *wait); + + /** Acquire dirty page ID archiver mutex. + It synchronizes concurrent start and stop operations by multiple clients. */ + void arch_mutex_enter() { mysql_mutex_lock(&m_mutex); } + + /** Release page ID archiver mutex */ + void arch_mutex_exit() { mysql_mutex_unlock(&m_mutex); } + + /** Acquire dirty page ID archive operation mutex. + It synchronizes concurrent page ID write to memory buffer. */ + void arch_oper_mutex_enter() { mysql_mutex_lock(&m_oper_mutex); } + + /** Release page ID archiver operatiion mutex */ + void arch_oper_mutex_exit() { mysql_mutex_unlock(&m_oper_mutex); } + + /* Save information at the time of a reset considered as the reset point. + @param[in] is_durable true if it's durable page tracking + @return true if the reset point information stored in the data block needs to + be flushed to disk before returning to the caller, else false */ + bool save_reset_point(bool is_durable); + + /** Wait for reset info to be flushed to disk. + @param[in] request_block block number until which blocks need to be + flushed + @return true if flushed, else false */ + bool wait_for_reset_info_flush(uint64_t request_block); + + /** Get the group which has tracked pages between the start_id and stop_id. + @param[in,out] start_id start LSN from which tracked pages are + required; updated to the actual start LSN used for the search + @param[in,out] stop_id stop_lsn until when tracked pages are + required; updated to the actual stop LSN used for the search + @param[out] group group which has the required tracked + pages, else nullptr. + @return error */ + int fetch_group_within_lsn_range(lsn_t &start_id, lsn_t &stop_id, + Arch_Group **group); + + /** Purge the archived files until the specified purge LSN. + @param[in] purge_lsn purge lsn until where files needs to be purged + @return error code + @retval 0 if purge was successful */ + uint purge(lsn_t *purge_lsn); + + /** Update the stop point in all the required structures. + @param[in] cur_blk block which needs to be updated with the stop info */ + void update_stop_info(Arch_Block *cur_blk); + + /** Fetch the status of the page tracking system. + @param[out] status vector of a pair of (ID, bool) where ID is the + start/stop point and bool is true if the ID is a start point else false */ + void get_status(std::vector> &status) + { + for (auto group : m_group_list) + group->get_status(status); + } + + /** Given start and stop position find number of pages tracked between them + @param[in] start_pos start position + @param[in] stop_pos stop position + @param[out] num_pages number of pages tracked between start and stop + position + @return false if start_pos and stop_pos are invalid else true */ + bool get_num_pages(Arch_Page_Pos start_pos, Arch_Page_Pos stop_pos, + uint64_t &num_pages); + + /** Get approximate number of tracked pages between two given LSN values. + @param[in,out] start_id fetch archived page Ids from this LSN + @param[in,out] stop_id fetch archived page Ids until this LSN + @param[out] num_pages number of pages tracked between specified + LSN range + @return error code */ + int get_num_pages(lsn_t &start_id, lsn_t &stop_id, uint64_t *num_pages); + + /** Get page IDs from a specific position. + Caller must ensure that read_len doesn't exceed the block. + @param[in] group group whose pages we're interested in + @param[in] read_pos position in archived data + @param[in] read_len amount of data to read + @param[out] read_buff buffer to return the page IDs. + @note Caller must allocate the buffer. + @return true if we could successfully read the block. */ + bool get_pages(Arch_Group *group, Arch_Page_Pos *read_pos, uint read_len, + byte *read_buff); + + /** Page tracking callback function. + @param[in] thd Current thread context + @param[in] buffer buffer filled with 8 byte page ids; the format is + specific to SE. For InnoDB it is space_id (4 bytes) followed by page number + (4 bytes) + @param[in] buf_len length of buffer in bytes + @param[in] num_pages number of valid page IDs in buffer + @param[in,out] user_ctx user context passed to page tracking function + @return Operation status. + */ + typedef int (*Page_Track_Callback)(MYSQL_THD thd, const unsigned char *buffer, + size_t buf_len, int num_pages, + void *user_ctx); + + /** Get archived page Ids between two given LSN values. + Attempt to read blocks directly from in memory buffer. If overwritten, + copy from archived files. + @param[in] thd thread handle + @param[in] cbk_func called repeatedly with page ID buffer + @param[in] cbk_ctx callback function context + @param[in,out] start_id fetch archived page Ids from this LSN + @param[in,out] stop_id fetch archived page Ids until this LSN + @param[in] buf buffer to fill page IDs + @param[in] buf_len buffer length in bytes + @return error code */ + int get_pages(MYSQL_THD thd, Page_Track_Callback cbk_func, void *cbk_ctx, + lsn_t &start_id, lsn_t &stop_id, byte *buf, uint buf_len); + + /** Set the latest stop LSN to the checkpoint LSN at the time it's called. */ + void post_recovery_init(); + + /** Recover the archiver system at the time of startup. Recover information + related to all the durable groups and start archiving if any group was active + at the time of crash/shutdown. + @return error code */ + dberr_t recover(); + +#ifdef UNIV_DEBUG + /** Print information related to the archiver for debugging purposes. */ + void print(); +#endif + + /** Set the state of the archiver system to read only. */ + void set_read_only_mode() { m_state = ARCH_STATE_READ_ONLY; } + + /** Check if archiver system is in initial state + @return true, if page ID archiver state is #ARCH_STATE_INIT */ + bool is_init() const { return (m_state == ARCH_STATE_INIT); } + + /** Check if archiver system is active + @return true, if page ID archiver state is #ARCH_STATE_ACTIVE or + #ARCH_STATE_PREPARE_IDLE. */ + bool is_active() const { + return (m_state == ARCH_STATE_ACTIVE || m_state == ARCH_STATE_PREPARE_IDLE); + } + + /** @return true if in abort state */ + bool is_abort() const { return (m_state == ARCH_STATE_ABORT); } + + /** Get the mutex protecting concurrent start, stop operations required + for initialising group during recovery. + @return mutex */ + mysql_mutex_t *get_mutex() { return (&m_mutex); } + + /** @return operation mutex */ + mysql_mutex_t *get_oper_mutex() { return (&m_oper_mutex); } + + /** Fetch the system client context. + @return system client context. */ + Page_Arch_Client_Ctx *get_sys_client() const { return (m_ctx); } + + /** @return the latest stop LSN */ + lsn_t get_latest_stop_lsn() const { return (m_latest_stop_lsn); } + + /** Disable copy construction */ + Arch_Page_Sys(Arch_Page_Sys const &) = delete; + + /** Disable assignment */ + Arch_Page_Sys &operator=(Arch_Page_Sys const &) = delete; + + private: + class Recovery; + + /** Wait for archive system to come out of #ARCH_STATE_PREPARE_IDLE. + If the system is preparing to idle, #start needs to wait + for it to come to idle state. + @return true, if successful + false, if needs to abort */ + bool wait_idle(); + + /** Check if the gap from last reset is short. + If not many page IDs are added till last reset, we avoid + taking a new reset point + @return true, if the gap is small. */ + bool is_gap_small(); + + /** Enable tracking pages in all buffer pools. + @param[in] tracking_lsn track pages from this LSN */ + void set_tracking_buf_pool(lsn_t tracking_lsn); + + /** Track pages for which IO is already started. */ + void track_initial_pages(); + + /** Flush the blocks to disk. + @param[out] wait true, if no more data to archive + @return error code */ + dberr_t flush_blocks(bool *wait); + + /** Flush all the blocks which are ready to be flushed but not flushed. + @param[out] cur_pos position of block which needs to be flushed + @param[in] end_pos position of block until which the blocks need to + be flushed + @return error code */ + dberr_t flush_inactive_blocks(Arch_Page_Pos &cur_pos, Arch_Page_Pos end_pos); + + /** Do a partial flush of the current active block + @param[in] cur_pos position of block which needs to be flushed + @param[in] partial_reset_block_flush true if reset block needs to be + flushed + @return error code */ + dberr_t flush_active_block(Arch_Page_Pos cur_pos, + bool partial_reset_block_flush); + + private: + /** Mutex protecting concurrent start, stop operations */ + mysql_mutex_t m_mutex; + + /** Archiver system state. */ + Arch_State m_state{ARCH_STATE_INIT}; + + /** List of log archive groups */ + Arch_Grp_List m_group_list{}; + + /** Position where last client started archiving */ + Arch_Page_Pos m_last_pos{}; + + /** LSN when last client started archiving */ + lsn_t m_last_lsn{LSN_MAX}; + + /** Latest LSN until where the tracked pages have been flushed. */ + lsn_t m_latest_stop_lsn{LSN_MAX}; + + /** LSN until where the groups are purged. */ + lsn_t m_latest_purged_lsn{LSN_MAX}; + + /** Mutex protecting concurrent operation on data */ + mysql_mutex_t m_oper_mutex; + + /** Current archive group */ + Arch_Group *m_current_group{nullptr}; + + /** In memory data buffer */ + ArchPageData m_data{}; + + /** Position to add new page ID */ + Arch_Page_Pos m_write_pos{}; + + /** Position to add new reset element */ + Arch_Page_Pos m_reset_pos{}; + + /** Position set to explicitly request the flush archiver to flush until + this position. + @note this is always increasing and is only updated by the requester thread + like checkpoint */ + Arch_Page_Pos m_request_flush_pos{}; + + /** Block number set to explicitly request the flush archiver to partially + flush the current active block with reset LSN. + @note this is always increasing and is only updated by the requester thread + like checkpoint */ + uint64_t m_request_blk_num_with_lsn{std::numeric_limits::max()}; + + /** Block number set once the flush archiver partially flushes the current + active block with reset LSN. + @note this is always increasing and is only updated by the requester thread + like checkpoint */ + uint64_t m_flush_blk_num_with_lsn{std::numeric_limits::max()}; + + /** Position for start flushing + @note this is always increasing and is only updated by the page archiver + thread */ + Arch_Page_Pos m_flush_pos{}; + + /** The index of the file the last reset belonged to. */ + uint m_last_reset_file_index{0}; + + /** System client. */ + Page_Arch_Client_Ctx *m_ctx; +}; + +/** Archiver System */ +class Arch_Sys +{ + public: + /** Initialize Page and Log archiver system. */ + Arch_Sys(); + + /** Free Page and Log archiver system */ + ~Arch_Sys(); + + /** Archiver background thread */ + static void archiver(); + + /** Initialize Page and Log archiver system + @return error code */ + static dberr_t init(); + + /** Free Page and Log archiver system */ + static void free(); + + /** Wait for archiver to stop during shutdown. */ + static void stop(); + + /** Remove files related to page and log archiving. + @param[in] file_path path to the file + @param[in] file_name name of the file */ + static void remove_file(const char *file_path, const char *file_name); + + /** Remove group directory and the files related to page and log archiving. + @param[in] dir_path path to the directory + @param[in] dir_name directory name */ + static void remove_dir(const char *dir_path, const char *dir_name); + + /** Start archiver background thread. + @return error code */ + int start_archiver(); + + /** Wake up archiver thread. + @return true iff still alive */ + bool signal_archiver(); + + /** Wait in archiver thread till signalled. */ + void archiver_wait(); + + /** Mark archiver stopped. */ + void archiver_stopped(); + + /** @return Log archiver system. */ + Arch_Log_Sys *log_sys() { return &m_log_sys; } + + /** @return Page archiver system. */ + Arch_Page_Sys *page_sys() { return &m_page_sys; } + + private: + /** Log archiver */ + Arch_Log_Sys m_log_sys; + + /** Page archiver */ + Arch_Page_Sys m_page_sys; + + /** Protect concurrent signal and thread operation. */ + mysql_mutex_t m_mutex; + + /** Archiver background thread wait condition. */ + mysql_cond_t m_cond; + + /** Archiver background thread is signalled. */ + bool m_signalled; + + /* Archiver background thread is running. */ + bool m_archiver_active; +}; + +/** Redo log and Dirty page ID archiver system global */ +extern Arch_Sys *arch_sys; + +#endif /* ARCH_ARCH_INCLUDE */ diff --git a/storage/innobase/include/arch0log.h b/storage/innobase/include/arch0log.h new file mode 100644 index 0000000000000..1eee0fa92da39 --- /dev/null +++ b/storage/innobase/include/arch0log.h @@ -0,0 +1,103 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/arch0log.h + Innodb interface for log archive + + *******************************************************/ + +#ifndef ARCH_LOG_INCLUDE +#define ARCH_LOG_INCLUDE + +#include "arch0arch.h" + +/** File Node Iterator callback +@param[in] file_name NULL terminated file name +@param[in] file_size size of file in bytes +@param[in] read_offset offset to start reading from +@param[in] ctx context passed by caller +@return error code */ +using Log_Arch_Cbk = int(char *file_name, uint64_t file_size, + uint64_t read_offset, void *ctx); + +/** Redo Log archiver client context */ +class Log_Arch_Client_Ctx { + public: + /** Constructor: Initialize elementsf */ + Log_Arch_Client_Ctx() + : m_state(ARCH_CLIENT_STATE_INIT), + m_group(nullptr), + m_begin_lsn(LSN_MAX), + m_end_lsn(LSN_MAX) {} + + /** Get redo file size for archived log file + @return size of file in bytes */ + os_offset_t get_archived_file_size() const; + + /** Get redo header and trailer size + @param[out] header_sz redo header size + @param[out] trailer_sz redo trailer size */ + void get_header_size(uint &header_sz, uint &trailer_sz) const; + + /** Start redo log archiving + @param[out] header buffer for redo header (caller must allocate) + @param[in] len buffer length + @return error code */ + int start(byte *header, uint len); + + /** Stop redo log archiving. Exact trailer length is returned as out + parameter which could be less than the redo block size. + @param[out] trailer redo trailer. Caller must allocate buffer. + @param[in,out] len trailer length + @param[out] offset trailer block offset + @return error code */ + int stop(byte *trailer, uint32_t &len, uint64_t &offset); + + /** Get archived data file details + @param[in] cbk_func callback called for each file + @param[in] ctx callback function context + @return error code */ + int get_files(Log_Arch_Cbk *cbk_func, void *ctx); + + /** Release archived data so that system can purge it */ + void release(); + + private: + /** Archiver client state */ + Arch_Client_State m_state; + + /** Archive group the client is attached to */ + Arch_Group *m_group; + + /** Start LSN for archived data */ + lsn_t m_begin_lsn; + + /** Stop LSN for archived data */ + lsn_t m_end_lsn; +}; + +#endif /* ARCH_LOG_INCLUDE */ diff --git a/storage/innobase/include/arch0page.h b/storage/innobase/include/arch0page.h new file mode 100644 index 0000000000000..fe96e21295992 --- /dev/null +++ b/storage/innobase/include/arch0page.h @@ -0,0 +1,270 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/arch0page.h + Innodb interface for modified page archive + + *******************************************************/ + +#ifndef ARCH_PAGE_INCLUDE +#define ARCH_PAGE_INCLUDE + +#include +#include "arch0arch.h" +#include "buf0buf.h" + +/** Archived page header file size (RESET Page) in number of blocks. */ +constexpr uint ARCH_PAGE_FILE_NUM_RESET_PAGE = 1; + +/** Archived file header size. No file header for this version. */ +constexpr uint ARCH_PAGE_FILE_HDR_SIZE = + ARCH_PAGE_FILE_NUM_RESET_PAGE * ARCH_PAGE_BLK_SIZE; + +/** @name Page Archive doublewrite buffer file name prefix and constant length +parameters. @{ */ + +/** Archive doublewrite buffer directory prefix */ +constexpr char ARCH_DBLWR_DIR[] = "ib_dblwr"; + +/** Archive doublewrite buffer file prefix */ +constexpr char ARCH_DBLWR_FILE[] = "dblwr_"; + +/** File name for the active file which indicates whether a group is active or +not. */ +constexpr char ARCH_PAGE_GROUP_ACTIVE_FILE_NAME[] = "active"; + +/** Archive doublewrite buffer number of files */ +constexpr uint ARCH_DBLWR_NUM_FILES = 1; + +/** Archive doublewrite buffer file capacity in no. of blocks */ +constexpr uint ARCH_DBLWR_FILE_CAPACITY = 3; + +/** @} */ + +/** @name Archive block header elements +@{ */ + +/** Block Header: Version is in first 1 byte. */ +constexpr uint ARCH_PAGE_BLK_HEADER_VERSION_OFFSET = 0; + +/** Block Header: Block Type is in next 1 byte. */ +constexpr uint ARCH_PAGE_BLK_HEADER_TYPE_OFFSET = 1; + +/** Block Header: Checksum is in next 4 bytes. */ +constexpr uint ARCH_PAGE_BLK_HEADER_CHECKSUM_OFFSET = 2; + +/** Block Header: Data length is in next 2 bytes. */ +constexpr uint ARCH_PAGE_BLK_HEADER_DATA_LEN_OFFSET = 6; + +/** Block Header: Stop LSN is in next 8 bytes */ +constexpr uint ARCH_PAGE_BLK_HEADER_STOP_LSN_OFFSET = 8; + +/** Block Header: Reset LSN is in next 8 bytes */ +constexpr uint ARCH_PAGE_BLK_HEADER_RESET_LSN_OFFSET = 16; + +/** Block Header: Block number is in next 8 bytes */ +constexpr uint ARCH_PAGE_BLK_HEADER_NUMBER_OFFSET = 24; + +/** Block Header: Total length. +Keep header length in multiple of #ARCH_BLK_PAGE_ID_SIZE */ +constexpr uint ARCH_PAGE_BLK_HEADER_LENGTH = 32; + +/** @} */ + +/** @name Page Archive reset block elements size. +@{ */ + +/** Serialized Reset ID: Reset LSN total size */ +constexpr uint ARCH_PAGE_FILE_HEADER_RESET_LSN_SIZE = 8; + +/** Serialized Reset ID: Reset block number size */ +constexpr uint ARCH_PAGE_FILE_HEADER_RESET_BLOCK_NUM_SIZE = 2; + +/** Serialized Reset ID: Reset block offset size */ +constexpr uint ARCH_PAGE_FILE_HEADER_RESET_BLOCK_OFFSET_SIZE = 2; + +/** Serialized Reset ID: Reset position total size */ +constexpr uint ARCH_PAGE_FILE_HEADER_RESET_POS_SIZE = + ARCH_PAGE_FILE_HEADER_RESET_BLOCK_NUM_SIZE + + ARCH_PAGE_FILE_HEADER_RESET_BLOCK_OFFSET_SIZE; + +/** @} */ + +/** @name Page Archive data block elements +@{ */ + +/** Serialized page ID: tablespace ID in First 4 bytes */ +constexpr uint ARCH_BLK_SPCE_ID_OFFSET = 0; + +/** Serialized page ID: Page number in next 4 bytes */ +constexpr uint ARCH_BLK_PAGE_NO_OFFSET = 4; + +/** Serialized page ID: Total length */ +constexpr uint ARCH_BLK_PAGE_ID_SIZE = 8; + +/** @} */ + +/** Number of memory blocks */ +constexpr uint ARCH_PAGE_NUM_BLKS = 32; + +/** Archived file format version */ +constexpr uint ARCH_PAGE_FILE_VERSION = 1; + +#ifdef UNIV_DEBUG +/** Archived page file default size in number of blocks. */ +extern uint ARCH_PAGE_FILE_CAPACITY; + +/** Archived page data file size (without header) in number of blocks. */ +extern uint ARCH_PAGE_FILE_DATA_CAPACITY; +#else +/** Archived page file default size in number of blocks. */ +constexpr uint ARCH_PAGE_FILE_CAPACITY = + (ARCH_PAGE_BLK_SIZE - ARCH_PAGE_BLK_HEADER_LENGTH) / ARCH_BLK_PAGE_ID_SIZE; + +/** Archived page data file size (without header) in number of blocks. */ +constexpr uint ARCH_PAGE_FILE_DATA_CAPACITY = + ARCH_PAGE_FILE_CAPACITY - ARCH_PAGE_FILE_NUM_RESET_PAGE; +#endif + +/** Threshold for page archive reset. Attach to current reset if the number of +tracked pages between the reset request and the current reset is less than this +threshold as we allow only one reset per data block. */ +constexpr uint ARCH_PAGE_RESET_THRESHOLD = + (ARCH_PAGE_BLK_SIZE - ARCH_PAGE_BLK_HEADER_LENGTH) / ARCH_BLK_PAGE_ID_SIZE; + +/** Callback for retrieving archived page IDs +@param[in] ctx context passed by caller +@param[in] buff buffer with page IDs +@param[in] num_pages number of page IDs in buffer +@return error code */ +using Page_Arch_Cbk = int(void *ctx, byte *buff, uint num_pages); + +/** Callback function to check if we need to wait for flush archiver to flush +more blocks */ +using Page_Wait_Flush_Archiver_Cbk = std::function; + +/** Dirty page archiver client context */ +class Page_Arch_Client_Ctx { + public: + /** Constructor: Initialize elements + @param[in] is_durable true if the client requires durability, else + false */ + Page_Arch_Client_Ctx(bool is_durable) : m_is_durable(is_durable) { + m_start_pos.init(); + m_stop_pos.init(); + mysql_mutex_init(0, &m_mutex, nullptr); + } + + /** Destructor. */ + ~Page_Arch_Client_Ctx() { mysql_mutex_destroy(&m_mutex); } + + /** Start dirty page tracking and archiving + @param[in] recovery true if the tracking is being started as part of + recovery process + @param[out] start_id fill the start lsn + @return error code. */ + int start(bool recovery, uint64_t *start_id); + + /** Stop dirty page tracking and archiving + @param[out] stop_id fill the stop lsn + @return error code. */ + int stop(uint64_t *stop_id); + + /** Release archived data so that system can purge it */ + void release(); + + /** Initialize context during recovery. + @param[in] group Group which needs to be attached to the client + @param[in] last_lsn last reset lsn + @return error code. */ + int init_during_recovery(Arch_Group *group, lsn_t last_lsn); + + /** Check if this client context is active. + @return true if active, else false */ + bool is_active() const { return (m_state == ARCH_CLIENT_STATE_STARTED); } + + /** Get archived page Ids. + Attempt to read blocks directly from in memory buffer. If overwritten, + copy from archived files. + @param[in] cbk_func called repeatedly with page ID buffer + @param[in] cbk_ctx callback function context + @param[in,out] buff buffer to fill page IDs + @param[in] buf_len buffer length in bytes + @return error code */ + int get_pages(Page_Arch_Cbk *cbk_func, void *cbk_ctx, byte *buff, + uint buf_len); + +#ifdef UNIV_DEBUG + /** Print information related to the archiver client for debugging purposes. + */ + void print(); +#endif + + /** Disable copy construction */ + Page_Arch_Client_Ctx(Page_Arch_Client_Ctx const &) = delete; + + /** Disable assignment */ + Page_Arch_Client_Ctx &operator=(Page_Arch_Client_Ctx const &) = delete; + + private: + /** Acquire client archiver mutex. + It synchronizes members on concurrent start and stop operations. */ + void arch_client_mutex_enter() { mysql_mutex_lock(&m_mutex); } + + /** Release client archiver mutex */ + void arch_client_mutex_exit() { mysql_mutex_unlock(&m_mutex); } + + private: + /** Page archiver client state */ + Arch_Client_State m_state{ARCH_CLIENT_STATE_INIT}; + + /** Archive group the client is attached to */ + Arch_Group *m_group{nullptr}; + + /** True if the client requires durablity */ + bool m_is_durable; + + /** Start LSN for archived data */ + lsn_t m_start_lsn{LSN_MAX}; + + /** Stop LSN for archived data */ + lsn_t m_stop_lsn{LSN_MAX}; + + /** Reset LSN at the time of last reset. */ + lsn_t m_last_reset_lsn{LSN_MAX}; + + /** Start position for client in archived file group */ + Arch_Page_Pos m_start_pos; + + /** Stop position for client in archived file group */ + Arch_Page_Pos m_stop_pos; + + /** Mutex protecting concurrent operation on data */ + mysql_mutex_t m_mutex; +}; + +#endif /* ARCH_PAGE_INCLUDE */ diff --git a/storage/innobase/include/arch0recv.h b/storage/innobase/include/arch0recv.h new file mode 100644 index 0000000000000..742f26e842669 --- /dev/null +++ b/storage/innobase/include/arch0recv.h @@ -0,0 +1,347 @@ +/***************************************************************************** + +Copyright (c) 2018, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License, version 2.0, +as published by the Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/**************************************************/ /** + @file include/arch0recv.h + Interface for crash recovery for page archiver system. + + *******************************************************/ + +#ifndef ARCH_RECV_INCLUDE +#define ARCH_RECV_INCLUDE + +#include "arch0arch.h" +#include "arch0page.h" +#include + +/** Info related to each group parsed at different stages of page archive +recovery. */ +class Arch_Recv_Group_Info { + public: + Arch_Recv_Group_Info() { + m_reset_pos.init(); + m_write_pos.init(); + + m_last_reset_block = static_cast(ut_zalloc(ARCH_PAGE_BLK_SIZE, + mem_key_archive)); + m_last_data_block = static_cast(ut_zalloc(ARCH_PAGE_BLK_SIZE, + mem_key_archive)); + } + + ~Arch_Recv_Group_Info() { + ut_free(m_last_reset_block); + ut_free(m_last_data_block); + } + + /** Disable assignment. */ + Arch_Recv_Group_Info &operator=(const Arch_Recv_Group_Info &) = delete; + + /** Disable copy construction. */ + Arch_Recv_Group_Info(const Arch_Recv_Group_Info &) = delete; + + /** Group data. */ + Arch_Group *m_group{nullptr}; + + /** Number of archived files belonging to the group. */ + uint m_num_files{0}; + + /** Group is active or not. */ + bool m_active{false}; + + /** True if group is from durable archiving, false if left over from a crash + during clone operation. */ + bool m_durable{false}; + + /** True if a new empty file was present in the group directory. + This can happen in case of a crash while writing to a new file. */ + bool m_new_empty_file{false}; + + /** The file index which is part of the file name may not necessarily + be 0 always. It's possible that purge might have purged files in the + group leading to the file index of the first file in the group being + greater than 0. So we need this info to know the index of the first + file in the group. */ + uint m_file_start_index{std::numeric_limits::max()}; + + /** Last reset position of the group. */ + Arch_Page_Pos m_reset_pos; + + /** Last write position of the group. */ + Arch_Page_Pos m_write_pos; + + /** Reset block of the last reset file in a group. */ + byte *m_last_reset_block{nullptr}; + + /** Data block of the last reset file in a group. */ + byte *m_last_data_block{nullptr}; + + /** Reset file structure of the last reset file */ + Arch_Reset_File m_last_reset_file; + + /** Start LSN of the group. */ + lsn_t m_start_lsn{LSN_MAX}; + + /** Last stop LSN of the group if active, else end LSN. */ + lsn_t m_last_stop_lsn{LSN_MAX}; +}; + +/** Mapping of group directory name to information related to the recovery +group info. */ +using Arch_Dir_Group_Info_Map = + std::unordered_map; + +/** Doublewrite buffer block along with their info. */ +struct Arch_Dblwr_Block { + /** Type of block flushed into the doublewrite block */ + Arch_Blk_Type m_block_type; + + /** Flush type of the block flushed into the doublewrite buffer */ + Arch_Blk_Flush_Type m_flush_type; + + /** Block number of the block flushed into the doublewrite buffer */ + uint32_t m_block_num; + + /** Doublewrite buffer block */ + byte *m_block; +}; + +/** Vector of doublewrite buffer blocks and their info. */ +using Arch_Dblwr_Blocks = std::vector; + +/** Doublewrite buffer context. */ +class Arch_Dblwr_Ctx { + public: + /** Constructor: Initialize members */ + Arch_Dblwr_Ctx() = default; + + ~Arch_Dblwr_Ctx() { + ut_free(m_buf); + m_file_ctx.close(); + } + + /** Initialize the doublewrite buffer. + @param[in] path path to the file + @param[in] base_file file name prefix + @param[in] num_files initial number of files + @param[in] file_size file size in bytes + @return error code. */ + dberr_t init(const char *path, const char *base_file, uint num_files, + uint64_t file_size); + + /** Read the doublewrite buffer file. + @return error code */ + dberr_t read_file(); + + /** Validate the blocks contained in the m_buf buffer and load only the valid + buffers into m_blocks. + @param[in] num_files number of files in the group information required to + validate the blocks */ + void validate_and_fill_blocks(size_t num_files); + + /** Get doubewrite buffer blocks. + @return doublewrite buffer blocks */ + Arch_Dblwr_Blocks blocks() { return m_blocks; } + + /** Disable copy construction. */ + Arch_Dblwr_Ctx(Arch_Dblwr_Ctx const &) = delete; + + /** Disable assignment. */ + Arch_Dblwr_Ctx &operator=(Arch_Dblwr_Ctx const &) = delete; + + private: + /** Buffer to hold the contents of the doublwrite buffer. */ + byte *m_buf{nullptr}; + + /** Total file size of the file which holds the doublewrite buffer. */ + uint64_t m_file_size{}; + + /** Doublewrite buffer file context. */ + Arch_File_Ctx m_file_ctx; + + /** List of doublewrite buffer blocks. */ + Arch_Dblwr_Blocks m_blocks{}; +}; + +/** Recovery system data structure for the archiver. */ +class Arch_Page_Sys::Recovery { + public: + /** Constructor: Initialize members + @param[in,out] page_sys global dirty page archive system + @param[in] dir_name main archiver directory name */ + Recovery(Arch_Page_Sys *page_sys, const char *dir_name) + : m_arch_dir_name(dir_name), m_page_sys(page_sys) {} + + /** Destructor: Close open file and free resources */ + ~Recovery() = default; + + /** Initialise the archiver's recovery system. + @return error code. */ + dberr_t init_dblwr(); + + /** Scan the archive directory and fetch all info related to group + directories and its files. + @return true if the scan was successful. */ + bool scan_for_groups(); + +#ifdef UNIV_DEBUG + /** Print information related to the archiver recovery system added + for debugging purposes. */ + void print(); +#endif + + /** Parse for group information and fill the group. + @return error code. */ + dberr_t recover(); + + /** Load archiver with the related data and start tracking if required. + @return error code. */ + dberr_t load_archiver(); + + /** Disable copy construction */ + Recovery(Recovery const &) = delete; + + /** Disable assignment */ + Recovery &operator=(Recovery const &) = delete; + + private: + /** Read all the group directories and store information related to them + required for parsing. + @param[in] file_path file path information */ + void read_group_dirs(const std::string file_path); + + /** Read all the archived files belonging to a group and store information + related to them required for parsing. + @param[in] dir_path dir path information + @param[in] file_path file path information */ + void read_group_files(const std::string dir_path, + const std::string file_path); + + private: + /** Archive directory. */ + std::string m_arch_dir_name; + + /** Global dirty page archive system */ + Arch_Page_Sys *m_page_sys; + + /** Doublewrite buffer context. */ + Arch_Dblwr_Ctx m_dblwr_ctx{}; + + /** Mapping of group directory names and group information related to + the group. */ + Arch_Dir_Group_Info_Map m_dir_group_info_map{}; +}; + +/** Recovery system data structure for the archiver. */ +class Arch_Group::Recovery { + public: + /** Constructor. + @param[in] group the parent class group object */ + Recovery(Arch_Group *group) { + ut_ad(group != nullptr); + m_group = group; + } + + /** Destructor. */ + ~Recovery() {} + + /** Check and replace blocks in archived files belonging to a group + from the doublewrite buffer if required. + @param[in] dblwr_ctx Doublewrite context which has the doublewrite + buffer blocks + @return error code */ + dberr_t replace_pages_from_dblwr(Arch_Dblwr_Ctx *dblwr_ctx); + + /** Delete the last file if there are no blocks flushed to it. + @param[in,out] info information related to group required for recovery + @return error code. */ + dberr_t cleanup_if_required(Arch_Recv_Group_Info &info); + + /** Start parsing the archive file for archive group information. + @param[in,out] info information related to group required for recovery + @return error code */ + dberr_t parse(Arch_Recv_Group_Info &info); + + /** Attach system client to the archiver during recovery if any group was + active at the time of crash. */ + void attach() { ++m_group->m_dur_ref_count; } + + /** Disable copy construction */ + Recovery(Recovery const &) = delete; + + /** Disable assignment */ + Recovery &operator=(Recovery const &) = delete; + + private: + /** The parent class group object. */ + Arch_Group *m_group{nullptr}; +}; + +/** Recovery system data structure for the archiver. */ +class Arch_File_Ctx::Recovery { + public: + /** Constructor. + @param[in] file_ctx file context to be used by this recovery class */ + Recovery(Arch_File_Ctx &file_ctx) : m_file_ctx(file_ctx) {} + + /** Destructor. */ + ~Recovery() {} + +#ifdef UNIV_DEBUG + /** Print recovery related data. + @param[in] file_start_index file index from where to begin */ + void reset_print(uint file_start_index); +#endif + + /** Fetch the reset points pertaining to a file. + @param[in] file_index file index of the file from which reset points + needs to be fetched + @param[in] last_file true if the file for which the stop point is + being fetched for is the last file + @param[in,out] info information related to group required for recovery + @return error code. */ + dberr_t parse_reset_points(uint file_index, bool last_file, + Arch_Recv_Group_Info &info); + + /** Fetch the stop lsn pertaining to a file. + @param[in] last_file true if the file for which the stop point is + being fetched for is the last file + @param[in,out] info information related to group required for recovery + @return error code. */ + dberr_t parse_stop_points(bool last_file, Arch_Recv_Group_Info &info); + + /** Disable copy construction */ + Recovery(Recovery const &) = delete; + + /** Disable assignment */ + Recovery &operator=(Recovery const &) = delete; + + private: + /** File context. */ + Arch_File_Ctx &m_file_ctx; +}; + +#endif /* ARCH_RECV_INCLUDE */ diff --git a/storage/innobase/include/buf0buf.h b/storage/innobase/include/buf0buf.h index 1716fc93cd960..cf110fa6a2ddf 100644 --- a/storage/innobase/include/buf0buf.h +++ b/storage/innobase/include/buf0buf.h @@ -474,6 +474,10 @@ class buf_page_t }; }; private: + /** Highest BIT in oldest_modification_ is set to indicate that the dirty + page is required to be tracked before flush. */ + static constexpr lsn_t S_PAGE_TRACK_BIT= 1ULL << 63; + /** log sequence number of the START of the log entry written of the oldest modification to this block which has not yet been written to the data file; @@ -682,16 +686,27 @@ class buf_page_t @retval 1 if the block is in buf_pool.flush_list but not modified @retval 2 if the block belongs to the temporary tablespace and has unwritten changes */ - lsn_t oldest_modification() const noexcept { return oldest_modification_; } + lsn_t oldest_modification() const noexcept + { + return oldest_modification_ & ~S_PAGE_TRACK_BIT; + } + /** @return true iff page is set for tracking. */ + bool marked_tracking() const noexcept + { + return oldest_modification_ & S_PAGE_TRACK_BIT; + } /** @return the log sequence number of the oldest pending modification, @retval 0 if the block is definitely not in buf_pool.flush_list @retval 1 if the block is in buf_pool.flush_list but not modified @retval 2 if the block belongs to the temporary tablespace and has unwritten changes */ lsn_t oldest_modification_acquire() const noexcept - { return oldest_modification_.load(std::memory_order_acquire); } + { + lsn_t oldest_lsn= oldest_modification_.load(std::memory_order_acquire); + return oldest_lsn & ~S_PAGE_TRACK_BIT; + } /** Set oldest_modification when adding to buf_pool.flush_list */ - inline void set_oldest_modification(lsn_t lsn) noexcept; + inline void set_oldest_modification(lsn_t lsn, bool track) noexcept; /** Clear oldest_modification after removing from buf_pool.flush_list */ inline void clear_oldest_modification() noexcept; /** Reset the oldest_modification when marking a persistent page freed */ @@ -1712,9 +1727,10 @@ class buf_pool_t /** Insert a modified block into the flush list. @param prev insert position (from prepare_insert_into_flush_list()) @param block modified block - @param lsn start LSN of the mini-transaction that modified the block */ + @param lsn start LSN of the mini-transaction that modified the block + @param mark_tracking mark the page for tracking */ inline void insert_into_flush_list(buf_page_t *prev, buf_block_t *block, - lsn_t lsn) noexcept; + lsn_t lsn, bool mark_tracking) noexcept; /** Free a page whose underlying file page has been freed. */ ATTRIBUTE_COLD void release_freed_page(buf_page_t *bpage) noexcept; @@ -1729,6 +1745,40 @@ class buf_pool_t @param pool_info buffer pool metadata */ void get_info(buf_pool_info_t *pool_info) noexcept; + /** Check if the page modifications are tracked. + @return true iff tracking is enabled and tracking lsn */ + std::pair is_tracking() const + { + auto track_lsn= track_page_lsn.load(); + return std::make_pair(track_lsn != LSN_MAX, track_lsn); + } + + /** Enable or Disable page tracking and set tracking LSN. + @param tracking_lsn Start LSN for tracking. LSN_MAX disables tracking. */ + void set_tracking(lsn_t tracking_lsn) { + ut_ad(tracking_lsn == LSN_MAX || track_page_lsn == LSN_MAX || + track_page_lsn <= tracking_lsn); + track_page_lsn= tracking_lsn; + } + + /* Set maximum LSN for which IO is started. Used for early termination of + for tracking pages in flush list for which IO has already started. + @param page_lsn oldest LSN for page which is being submitted for write IO */ + void set_max_lsn_io(lsn_t page_lsn) + { + if (page_lsn > max_lsn_io) + max_lsn_io= page_lsn; + } + + /** Check if current page LSN is more than maximum LSN for which IO is + already started. + @param page_lsn oldest LSN for page which is being submitted for write IO + @return true iff page LSN value is more. */ + bool is_lsn_more_than_max_io_lsn(lsn_t page_lsn) + { + return page_lsn > max_lsn_io; + } + private: /** Temporary memory for page_compressed and encrypted I/O */ struct io_buf_t @@ -1745,6 +1795,14 @@ class buf_pool_t /** Reserve a buffer */ buf_tmp_buffer_t *reserve(bool wait_for_reads) noexcept; } io_buf; + + /** Page Tracking start LSN. Read-Write is protected by buffer pool mutex. + During mtr commit we do atomic access to set tracking flag. */ + Atomic_relaxed track_page_lsn; + + /** Maximum LSN for which write io has already started. Read-Write is + protected by buffer pool mutex. */ + lsn_t max_lsn_io; }; /** The InnoDB buffer pool */ @@ -1818,11 +1876,12 @@ inline void buf_page_t::set_corrupt_id() noexcept } /** Set oldest_modification when adding to buf_pool.flush_list */ -inline void buf_page_t::set_oldest_modification(lsn_t lsn) noexcept +inline void buf_page_t::set_oldest_modification(lsn_t lsn, bool track) noexcept { mysql_mutex_assert_owner(&buf_pool.flush_list_mutex); ut_ad(oldest_modification() <= 1); ut_ad(lsn > 2); + if (track) lsn|= S_PAGE_TRACK_BIT; oldest_modification_= lsn; } diff --git a/storage/innobase/include/buf0dump.h b/storage/innobase/include/buf0dump.h index 485869007bee3..7c7c3d30bd05c 100644 --- a/storage/innobase/include/buf0dump.h +++ b/storage/innobase/include/buf0dump.h @@ -26,6 +26,7 @@ Created April 08, 2011 Vasil Dimov #ifndef buf0dump_h #define buf0dump_h +#include /** Start the buffer pool dump/load task and instructs it to start a dump. */ void buf_dump_start(); @@ -41,4 +42,9 @@ void buf_load_at_startup(); /** Wait for currently running load/dumps to finish*/ void buf_load_dump_end(); +/** 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). */ +void buf_dump_generate_path(char *path, size_t path_size); + #endif /* buf0dump_h */ diff --git a/storage/innobase/include/buf0flu.h b/storage/innobase/include/buf0flu.h index 5d01d38ba21a9..1b70a73fe1cf9 100644 --- a/storage/innobase/include/buf0flu.h +++ b/storage/innobase/include/buf0flu.h @@ -61,6 +61,11 @@ void buf_page_write_complete(const IORequest &request, bool error) noexcept; @param[in,out] page page to be updated */ void buf_flush_assign_full_crc32_checksum(byte* page) noexcept; +/** Check if page type is uncompressed. +@param[in] page page frame +@return true if uncompressed page type. */ +bool page_is_uncompressed_type(const byte *page); + /** Initialize a page for writing to the tablespace. @param[in] block buffer block; NULL if bypassing the buffer pool @param[in,out] page page frame diff --git a/storage/innobase/include/clone0api.h b/storage/innobase/include/clone0api.h new file mode 100644 index 0000000000000..b5f84453ae476 --- /dev/null +++ b/storage/innobase/include/clone0api.h @@ -0,0 +1,238 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/clone0api.h + Innodb Clone Interface + + *******************************************************/ + +#ifndef CLONE_API_INCLUDE +#define CLONE_API_INCLUDE + +#include "univ.i" +#ifndef UNIV_HOTBACKUP +#include "handler.h" +#include + +using space_id_t = decltype(fil_space_t::id); +using page_no_t = uint32_t; + +/** Get capability flags for clone operation +@param[out] flags capability flag */ +void innodb_clone_get_capability(Ha_clone_flagset &flags); + +/** Begin copy from source database +@param[in] thd server thread handle +@param[in,out] loc locator +@param[in,out] loc_len locator length +@param[out] task_id task identifier +@param[in] type clone type +@param[in] mode mode for starting clone +@return error code */ +int innodb_clone_begin(THD *thd, const byte *&loc, uint &loc_len, + uint &task_id, Ha_clone_type type, Ha_clone_mode mode); + +/** Copy data from source database in chunks via callback +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] stage clone execution Stage +@param[in] cbk callback interface for sending data +@return error code */ +int innodb_clone_copy(THD *thd, const byte *loc, uint loc_len, + uint task_id, Ha_clone_stage stage, Ha_clone_cbk *cbk); + +/** Acknowledge data to source database +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err inform any error occurred +@param[in] cbk callback interface for receiving data +@return error code */ +int innodb_clone_ack(THD *thd, const byte *loc, uint loc_len, + uint task_id, int in_err, Ha_clone_cbk *cbk); + +/** End copy from source database +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err error code when ending after error +@return error code */ +int innodb_clone_end(THD *thd, const byte *loc, uint loc_len, + uint task_id, int in_err); + +/** Begin apply to destination database +@param[in] thd server thread handle +@param[in,out] loc locator +@param[in,out] loc_len locator length +@param[out] task_id task identifier +@param[in] mode mode for starting clone +@param[in] data_dir target data directory +@return error code */ +int innodb_clone_apply_begin(THD *thd, const byte *&loc, uint &loc_len, + uint &task_id, Ha_clone_mode mode, + const char *data_dir); + +/** Apply data to destination database in chunks via callback +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err inform any error occurred +@param[in] cbk callback interface for receiving data +@return error code */ +int innodb_clone_apply(THD *thd, const byte *loc, uint loc_len, uint task_id, + int in_err, Ha_clone_cbk *cbk); + +/** End apply to destination database +@param[in] thd server thread handle +@param[in] loc locator +@param[in] loc_len locator length in bytes +@param[in] task_id task identifier +@param[in] in_err error code when ending after error +@return error code */ +int innodb_clone_apply_end(THD *thd, const byte *loc, uint loc_len, + uint task_id, int in_err); + +/** Check and delete any old list files. */ +void clone_init_list_files(); + +/** Add file name to clone list file for future replacement or rollback. +@param[in] list_file_name list file name where to add the file +@param[in] file_name file name to add to the list +@return error code */ +int clone_add_to_list_file(const char *list_file_name, const char *file_name); + +/** Remove one of the clone list files. +@param[in] file_name list file name to delete */ +void clone_remove_list_file(const char *file_name); + +/** Revert back clone changes in case of an error. */ +void clone_files_error(); + +#ifdef UNIV_DEBUG +/** Debug function to check and crash during recovery. +@param[in] is_cloned_db if cloned database recovery */ +bool clone_check_recovery_crashpoint(bool is_cloned_db); +#endif + +/** Change cloned file states during recovery. +@param[in] finished if recovery is finishing */ +void clone_files_recovery(bool finished); + +/** Initialize Clone system +@return inndodb error code */ +dberr_t clone_init(); + +/** Uninitialize Clone system */ +void clone_free(); + +/** Check if active clone is running. +@return true, if any active clone is found. */ +bool clone_check_active(); + +/** @return true, if clone provisioning in progress. */ +bool clone_check_provisioning(); +#endif /* !UNIV_HOTBACKUP */ + +/** Clone Notification handler. */ +class Clone_notify { + public: + /** Notification type. Currently used by various DDL commands. */ + enum class Type { + /* Special consideration is needed for UNDO as these DDLs + don't use DDL log and needs special consideration during recovery. */ + SPACE_UNDO_TRUNCATE, + /* Redo log resizing */ + SYSTEM_REDO_RESIZE + }; + +#ifdef UNIV_HOTBACKUP + Clone_notify(Type, space_id_t, bool) : m_error() {} + ~Clone_notify() {} +#else + /** Constructor to initiate notification. + @param[in] type notification type + @param[in] space tablespace ID for which notification is sent + @param[in] no_wait set error and return immediately if needs to wait */ + Clone_notify(Type type, space_id_t space, bool no_wait); + + /** Destructor to automatically end notification. */ + ~Clone_notify(); +#endif /* UNIV_HOTBACKUP */ + + /** Get notification message for printing. + @param[in] begin true if notification begin otherwise end + @param[out] mesg notification message */ + void get_mesg(bool begin, std::string &mesg); + + /** @return true iff notification failed. */ + bool failed() const { return m_error != 0; } + + /** @return saved error code. */ + int get_error() const { return m_error; } + + /** Disable copy construction */ + Clone_notify(Clone_notify &) = delete; + + /** Disable assignment */ + Clone_notify &operator=(Clone_notify const &) = delete; + + private: + /** Notification wait type set. */ + enum class Wait_at { + /* Clone doesn't need to wait. */ + NONE, + /* Clone needs to wait before entering. */ + ENTER, + /* Clone needs to wait before state change. */ + STATE_CHANGE, + /* Clone needs to abort. */ + ABORT + }; + + private: + /** Tablespace ID for which notification is sent. */ + space_id_t m_space_id; + + /** Notification type. */ + Type m_type; + + /** Wait type set. */ + Wait_at m_wait; + + /** Blocked clone state if clone is blocked. */ + uint32_t m_blocked_state; + + /** Saved error. */ + int m_error; +}; + +#endif /* CLONE_API_INCLUDE */ diff --git a/storage/innobase/include/clone0clone.h b/storage/innobase/include/clone0clone.h new file mode 100644 index 0000000000000..e39041ce46fc3 --- /dev/null +++ b/storage/innobase/include/clone0clone.h @@ -0,0 +1,1361 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/clone0clone.h + Innodb Clone System + + *******************************************************/ + +#ifndef CLONE_CLONE_INCLUDE +#define CLONE_CLONE_INCLUDE + +#include +#include "db0err.h" +#include "my_global.h" +#include "mysql/plugin.h" // thd_killed() +#include "handler.h" +#include "univ.i" + +#include "clone0api.h" +#include "clone0desc.h" +#include "clone0snapshot.h" + +#include +#include + +/** Directory under data directory for all clone status files. */ +#define CLONE_FILES_DIR_NAME OS_FILE_PREFIX "clone" +#define CLONE_FILES_DIR CLONE_FILES_DIR_NAME OS_PATH_SEPARATOR_STR + +/** Clone in progress file name length. */ +const size_t CLONE_INNODB_FILE_LEN = 64; + +#ifdef UNIV_DEBUG +/** Clone simulate recovery error file name. */ +const char CLONE_INNODB_RECOVERY_CRASH_POINT[] = + CLONE_FILES_DIR OS_FILE_PREFIX "status_crash_point"; +#endif + +/** Clone in progress file name. */ +const char CLONE_INNODB_IN_PROGRESS_FILE[] = + CLONE_FILES_DIR OS_FILE_PREFIX "status_in_progress"; + +/** Clone error file name. */ +const char CLONE_INNODB_ERROR_FILE[] = + CLONE_FILES_DIR OS_FILE_PREFIX "status_error"; + +/** Clone fix up file name. Present when clone needs table fix up. */ +const char CLONE_INNODB_FIXUP_FILE[] = + CLONE_FILES_DIR OS_FILE_PREFIX "status_fix"; + +/** Clone recovery status. */ +const char CLONE_INNODB_RECOVERY_FILE[] = + CLONE_FILES_DIR OS_FILE_PREFIX "status_recovery"; + +/** Clone file name for list of files cloned in place. */ +const char CLONE_INNODB_NEW_FILES[] = + CLONE_FILES_DIR OS_FILE_PREFIX "new_files"; + +/** Clone file name for list of files to be replaced. */ +const char CLONE_INNODB_REPLACED_FILES[] = + CLONE_FILES_DIR OS_FILE_PREFIX "replace_files"; + +/** Clone file name for list of old files to be removed. */ +const char CLONE_INNODB_OLD_FILES[] = + CLONE_FILES_DIR OS_FILE_PREFIX "old_files"; + +/** Clone file name for list of temp files renamed by ddl. */ +const char CLONE_INNODB_DDL_FILES[] = + CLONE_FILES_DIR OS_FILE_PREFIX "ddl_files"; + +/** Clone file extension for files to be replaced. */ +const char CLONE_INNODB_REPLACED_FILE_EXTN[] = "." OS_FILE_PREFIX "clone"; + +/** Clone file extension for saved old files. */ +const char CLONE_INNODB_SAVED_FILE_EXTN[] = "." OS_FILE_PREFIX "clone_save"; + +/** Clone file extension for temporary renamed file. */ +const char CLONE_INNODB_DDL_FILE_EXTN[] = "." OS_FILE_PREFIX "clone_ddl"; + +using Clone_Msec = std::chrono::milliseconds; +using Clone_Sec = std::chrono::seconds; +using Clone_Min = std::chrono::minutes; + +/** Default sleep time while waiting: 100 ms */ +const Clone_Msec CLONE_DEF_SLEEP{100}; + +/** Default alert interval in multiple of sleep time: 5 seconds */ +const Clone_Sec CLONE_DEF_ALERT_INTERVAL{5}; + +/** Default timeout in multiple of sleep time: 30 minutes */ +const Clone_Min CLONE_DEF_TIMEOUT{30}; + +/** Clone system state */ +enum Clone_System_State { + CLONE_SYS_INACTIVE, + CLONE_SYS_ACTIVE, + CLONE_SYS_ABORT +}; + +using Clone_Sys_State = std::atomic; + +/** Clone Handle State */ +enum Clone_Handle_State { + CLONE_STATE_INIT = 1, + CLONE_STATE_ACTIVE, + CLONE_STATE_IDLE, + CLONE_STATE_ABORT +}; + +/** Clone task state */ +enum Clone_Task_State { CLONE_TASK_INACTIVE = 1, CLONE_TASK_ACTIVE }; + +/** Maximum number of concurrent snapshots */ +const int MAX_SNAPSHOTS = 1; + +/** Maximum number of concurrent clones */ +const int MAX_CLONES = 1; + +/** Clone system array size */ +const int CLONE_ARR_SIZE = 2 * MAX_CLONES; + +/** Snapshot system array size */ +const int SNAPSHOT_ARR_SIZE = 2 * MAX_SNAPSHOTS; + +/** Task for clone operation. Multiple task can concurrently work +on a clone operation. */ +struct Clone_Task { + /** Task Meta data */ + Clone_Task_Meta m_task_meta; + + /** Task state */ + Clone_Task_State m_task_state; + + /** Serial descriptor byte string */ + byte *m_serial_desc; + + /** Serial descriptor allocated length */ + uint m_alloc_len; + + /** If task is currently pinning file. Before opening + the file we must have a pin on file metadata. */ + bool m_pinned_file; + + /** Current file descriptor */ + pfs_os_file_t m_current_file_des; + + /** Current file index */ + uint m_current_file_index; + + /** Data files are read using OS buffer cache */ + bool m_file_cache; + + /** If master task */ + bool m_is_master; + + /** If task has associated session */ + bool m_has_thd; + +#ifdef UNIV_DEBUG + /** Ignore debug sync point */ + bool m_ignore_sync; + + /** Counter to restart in different state */ + int m_debug_counter; +#endif /* UNIV_DEBUG */ + + /** Allocated buffer */ + byte *m_current_buffer; + + /** Allocated buffer length */ + uint m_buffer_alloc_len; + + /** Data transferred for current chunk in bytes */ + uint32_t m_data_size; +}; + +class Clone_Handle; + +/** Task manager for manging the tasks for a clone operation */ +class Clone_Task_Manager { + public: + /** Initialize task manager for clone handle + @param[in] snapshot snapshot */ + void init(Clone_Snapshot *snapshot); + + /** Get task state mutex + @return state mutex */ + mysql_mutex_t *get_mutex() { return (&m_state_mutex); } + + /** Handle any error raised by concurrent tasks. + @param[in] raise_error raise error if true + @return error code */ + int handle_error_other_task(bool raise_error); + + /** Set error number + @param[in] err error number + @param[in] file_name associated file name if any */ + void set_error(int err, const char *file_name) { + mysql_mutex_lock(&m_state_mutex); + + ib::info() << "Clone Set Error code: " << err + << " Saved Error code: " << m_saved_error; + + /* Override any network error as we should not be waiting for restart + if other errors have occurred. */ + if (m_saved_error == 0 || is_network_error(m_saved_error)) { + m_saved_error = err; + + if (file_name != nullptr) { + m_err_file_name.assign(file_name); + } + } + + mysql_mutex_unlock(&m_state_mutex); + } + + /** Add a task to task manager + @param[in] thd server THD object + @param[in] ref_loc reference locator from remote + @param[in] loc_len locator length in bytes + @param[out] task_id task identifier + @return error code */ + int add_task(THD *thd, const byte *ref_loc, uint loc_len, uint &task_id); + + /** Drop task from task manager + @param[in] thd server THD object + @param[in] task_id current task ID + @param[out] is_master true, if master task + @return true if needs to wait for re-start */ + bool drop_task(THD *thd, uint task_id, bool &is_master); + + /** Check if chunk is already reserved. + @param[in] chunk_num chunk number + @return true, iff chunk is reserved. */ + bool is_chunk_reserved(uint32_t chunk_num) { + return m_chunk_info.m_reserved_chunks[chunk_num]; + } + + /** Reset chunk information for task + @param[in] task current task */ + void reset_chunk(Clone_Task *task) { + mysql_mutex_assert_owner(&m_state_mutex); + /* Reset current processing chunk */ + task->m_task_meta.m_chunk_num = 0; + task->m_task_meta.m_block_num = 0; + + if (task->m_data_size > 0) { + ut_ad(get_state() != CLONE_SNAPSHOT_NONE); + ut_ad(get_state() != CLONE_SNAPSHOT_INIT); + ut_ad(get_state() != CLONE_SNAPSHOT_DONE); + + auto &monitor = m_clone_snapshot->get_clone_monitor(); + + monitor.update_work(task->m_data_size); + } + + task->m_data_size = 0; + } + + /** Get task by index + @param[in] index task index + @return task */ + Clone_Task *get_task_by_index(uint index) { + auto task = (m_clone_tasks + index); + ut_ad(task->m_task_state == CLONE_TASK_ACTIVE); + + return (task); + } + + /** Reserve next chunk from task manager. Called by individual tasks. + @param[in] task requesting task + @param[out] ret_chunk reserved chunk number + @param[out] ret_block start block number + '0' if no more chunk. + @return error code */ + int reserve_next_chunk(Clone_Task *task, uint32_t &ret_chunk, + uint32_t &ret_block); + + /** Set current chunk and block information + @param[in,out] task requesting task + @param[in] new_meta updated task metadata + @return error code */ + int set_chunk(Clone_Task *task, Clone_Task_Meta *new_meta); + + /** Track any incomplete chunks handled by the task + @param[in,out] task current task */ + void add_incomplete_chunk(Clone_Task *task); + + /** Initialize task manager for current state */ + void init_state(); + + /** Re-initialize task manager for current state */ + void reinit_state(); + + /** Reinitialize state using locator + @param[in] loc locator from remote client + @param[in] loc_len locator length in bytes */ + void reinit_copy_state(const byte *loc, uint loc_len); + + /** Reinitialize state using locator + @param[in] ref_loc current locator + @param[in] ref_len current locator length + @param[out] new_loc new locator to be sent to remote server + @param[out] new_len length of new locator + @param[in,out] alloc_len allocated length for locator buffer */ + void reinit_apply_state(const byte *ref_loc, uint ref_len, byte *&new_loc, + uint &new_len, uint &alloc_len); + + /** Reset state transition information */ + void reset_transition() { + m_num_tasks_transit = 0; + m_num_tasks_finished = 0; + m_next_state = CLONE_SNAPSHOT_NONE; + } + + /** Reset error information */ + void reset_error() { + m_saved_error = 0; + m_err_file_name.assign("Clone File"); + } + + /** Get current clone state + @return clone state */ + Snapshot_State get_state() { return (m_current_state); } + + /** Check if in state transition + @return true if state transition is in progress */ + bool in_transit_state() { return (m_next_state != CLONE_SNAPSHOT_NONE); } + + /** Get attached snapshot + @return snapshot */ + Clone_Snapshot *get_snapshot() { return (m_clone_snapshot); } + + /** Move to next snapshot state. Each task must call this after + no more chunk is left in current state. The state can be changed + only after all tasks have finished transferring the reserved chunks. + @param[in] task clone task + @param[in] state_desc descriptor for next state + @param[in] new_state next state to move to + @param[in] cbk alert callback for long wait + @param[out] num_wait unfinished tasks in current state + @return error code */ + int change_state(Clone_Task *task, Clone_Desc_State *state_desc, + Snapshot_State new_state, Clone_Alert_Func cbk, + uint &num_wait); + + /** Check if state transition is over and all tasks moved to next state + @param[in] task requesting task + @param[in] new_state next state to move to + @param[in] exit_on_wait exit from transition if needs to wait + @param[in] in_err input error if already occurred + @param[out] num_wait number of tasks to move to next state + @return error code */ + int check_state(Clone_Task *task, Snapshot_State new_state, bool exit_on_wait, + int in_err, uint32_t &num_wait); + + /** Check if needs to send state metadata once + @param[in] task current task + @return true if needs to send state metadata */ + bool is_restart_metadata(Clone_Task *task) { + if (task->m_is_master && m_send_state_meta) { + m_send_state_meta = false; + return (true); + } + + return (false); + } + + /** @return true if file metadata is transferred */ + bool is_file_metadata_transferred() const { + return (m_transferred_file_meta); + } + + /** Set sub-state: all file metadata is transferred */ + void set_file_meta_transferred() { m_transferred_file_meta = true; } + + /** Mark state finished for current task + @param[in] task current task + @return error code */ + int finish_state(Clone_Task *task); + + /** Set acknowledged state + @param[in] state_desc State descriptor */ + void ack_state(const Clone_Desc_State *state_desc); + + /** Wait for acknowledgement + @param[in] clone parent clone handle + @param[in] task current task + @param[in] callback user callback interface + @return error code */ + int wait_ack(Clone_Handle *clone, Clone_Task *task, Ha_clone_cbk *callback); + + /** Check if state ACK is needed + @param[in] state_desc State descriptor + @return true if need to wait for ACK from remote */ + bool check_ack(const Clone_Desc_State *state_desc) { + bool ret = true; + + mysql_mutex_lock(&m_state_mutex); + + /* Check if state is already acknowledged */ + if (m_ack_state == state_desc->m_state) { + ut_ad(m_restart_count > 0); + ret = false; + ++m_num_tasks_finished; + } + + mysql_mutex_unlock(&m_state_mutex); + + return (ret); + } + + /** Check if clone is restarted after failure + @return true if restarted */ + bool is_restarted() { return (m_restart_count > 0); } + + /** Allocate buffers for current task + @param[in,out] task current task + @return error code */ + int alloc_buffer(Clone_Task *task); + +#ifdef UNIV_DEBUG + /** Check if needs to wait for debug sync point + @param[in] chunk_num chunk number to process + @param[in] task current task + @return true, if clone needs to check and wait */ + bool debug_sync_check(uint32_t chunk_num, Clone_Task *task); + + /** Wait during clone operation + @param[in] chunk_num chunk number to process + @param[in] task current task */ + void debug_wait(uint chunk_num, Clone_Task *task); + + /** Wait before sending DDL metadata. */ + void debug_wait_ddl_meta(); + + /** Force restart clone operation by raising network error + @param[in] task current task + @param[in] in_err any err that has occurred + @param[in] restart_count restart counter + @return error code */ + int debug_restart(Clone_Task *task, int in_err, int restart_count); + + /** @return clone master task. */ + Clone_Task *find_master_task(); +#endif /* UNIV_DEBUG */ + + private: + /** Check if we need to wait before adding current task + @param[in] ref_loc reference locator from remote + @param[in] loc_len reference locator length + @return true, if needs to wait */ + bool wait_before_add(const byte *ref_loc, uint loc_len); + + private: + /** Check if network error + @param[in] err error code + @return true if network error */ + bool is_network_error(int err) { + if (err == ER_NET_ERROR_ON_WRITE || err == ER_NET_READ_ERROR || + err == ER_NET_WRITE_INTERRUPTED || err == ER_NET_READ_INTERRUPTED) + return true; + + return false; + } + + /** Reserve free task from task manager and initialize + @param[in] thd server THD object + @param[out] task_id initialized task ID */ + void reserve_task(THD *thd, uint &task_id); + + /** Check if we should process incomplete chunk next. Incomplete + chunks could be there after a re-start from network failure. We always + process the chunks in order and need to choose accordingly. + @return if need to process incomplete chunk next. */ + inline bool process_inclomplete_chunk() { + /* 1. Check if there is any incomplete chunk. */ + auto &chunks = m_chunk_info.m_incomplete_chunks; + if (chunks.empty()) { + return (false); + } + + /* 2. Check if all complete chunks are processed. */ + auto min_complete_chunk = m_chunk_info.m_min_unres_chunk; + if (min_complete_chunk > m_chunk_info.m_total_chunks) { + return (true); + } + + /* 3. Compare the minimum chunk number for complete and incomplete chunk */ + auto it = chunks.begin(); + auto min_incomplete_chunk = it->first; + + ut_ad(min_complete_chunk != min_incomplete_chunk); + return (min_incomplete_chunk < min_complete_chunk); + } + + /** Get next in complete chunk if any + @param[out] block_num first block number in chunk + @return incomplete chunk number */ + uint32_t get_next_incomplete_chunk(uint32_t &block_num); + + /** Get next unreserved chunk + @return chunk number */ + uint32_t get_next_chunk(); + + private: + /** Mutex synchronizing access by concurrent tasks */ + mysql_mutex_t m_state_mutex; + + /** Finished and incomplete chunk information */ + Chunk_Info m_chunk_info; + + /** Clone task array */ + Clone_Task m_clone_tasks[CLONE_MAX_TASKS]; + + /** Current number of tasks */ + uint m_num_tasks; + + /** Number of tasks finished current state */ + uint m_num_tasks_finished; + + /** Number of tasks in transit state */ + uint m_num_tasks_transit; + + /** Number of times clone is restarted */ + uint m_restart_count; + + /** Acknowledged state from client */ + Snapshot_State m_ack_state; + + /** Current state for clone */ + Snapshot_State m_current_state; + + /** Next state: used during state transfer */ + Snapshot_State m_next_state; + + /* Sub state: File metadata is transferred */ + bool m_transferred_file_meta; + + /** Send state metadata before starting: Used for restart */ + bool m_send_state_meta; + + /** Save any error raised by a task */ + int m_saved_error; + + /** File name related to the saved error */ + std::string m_err_file_name; + + /** Attached snapshot handle */ + Clone_Snapshot *m_clone_snapshot; +}; + +/** Clone Handle for copying or applying data */ +class Clone_Handle { + public: + /** Construct clone handle + @param[in] handle_type clone handle type + @param[in] clone_version clone version + @param[in] clone_index index in clone array */ + Clone_Handle(Clone_Handle_Type handle_type, uint clone_version, + uint clone_index); + + /** Destructor: Detach from snapshot */ + ~Clone_Handle(); + + /** Initialize clone handle + @param[in] ref_loc reference locator + @param[in] ref_len reference locator length + @param[in] type clone type + @param[in] data_dir data directory for apply + @return error code */ + int init(const byte *ref_loc, uint ref_len, Ha_clone_type type, + const char *data_dir); + + /** Attach to the clone handle */ + void attach() { ++m_ref_count; } + + /** Detach from the clone handle + @return reference count */ + uint detach() { + ut_a(m_ref_count > 0); + --m_ref_count; + + return (m_ref_count); + } + + /** Get locator for the clone handle. + @param[out] loc_len serialized locator length + @return serialized clone locator */ + byte *get_locator(uint &loc_len); + + /** @return clone data directory */ + const char *get_datadir() const { return (m_clone_dir); } + + /** @return true, if clone is replacing current data directory. */ + bool replace_datadir() const { + return (!is_copy_clone() && m_clone_dir == nullptr); + } + + /** Build locator descriptor for the clone handle + @param[out] loc_desc locator descriptor */ + void build_descriptor(Clone_Desc_Locator *loc_desc); + + /** Add a task to clone handle + @param[in] thd server THD object + @param[in] ref_loc reference locator from remote + @param[in] ref_len reference locator length + @param[out] task_id task identifier + @return error code */ + int add_task(THD *thd, const byte *ref_loc, uint ref_len, uint &task_id) { + return (m_clone_task_manager.add_task(thd, ref_loc, ref_len, task_id)); + } + + /** Drop task from clone handle + @param[in] thd server THD object + @param[in] task_id current task ID + @param[out] is_master true, if master task + @return true if needs to wait for re-start */ + bool drop_task(THD *thd, uint task_id, bool &is_master); + + /** Save current error number + @param[in] err error number */ + void save_error(int err) { + if (err != 0) { + m_clone_task_manager.set_error(err, nullptr); + } + } + + /** Check for error from other tasks and DDL + @param[in,out] thd session THD + @return error code */ + int check_error(THD *thd) { + bool has_thd = (thd != nullptr); + auto err = m_clone_task_manager.handle_error_other_task(has_thd); + /* Save any error reported */ + save_error(err); + return (err); + } + + /** @return true if any task is interrupted */ + bool is_interrupted() { + auto err = m_clone_task_manager.handle_error_other_task(false); + return (err == ER_QUERY_INTERRUPTED); + } + + /** Get clone handle index in clone array + @return array index */ + uint get_index() { return (m_clone_arr_index); } + + /** Get clone data descriptor version + @return version */ + uint get_version() { return (m_clone_desc_version); } + + /** @return active snapshot */ + Clone_Snapshot *get_snapshot() { return m_clone_task_manager.get_snapshot(); } + + /** Check if it is copy clone + @return true if copy clone handle */ + bool is_copy_clone() const { return (m_clone_handle_type == CLONE_HDL_COPY); } + + /** Check if clone type matches + @param[in] other_handle_type type to match with + @return true if type matches with clone handle type */ + bool match_hdl_type(Clone_Handle_Type other_handle_type) { + return (m_clone_handle_type == other_handle_type); + } + + /** Set current clone state + @param[in] state clone handle state */ + void set_state(Clone_Handle_State state) { m_clone_handle_state = state; } + + /** Set clone to ABORT state end any attached snapshot. */ + void set_abort(); + + /** Check if clone state is active + @return true if in active state */ + bool is_active() { return (m_clone_handle_state == CLONE_STATE_ACTIVE); } + + /** Check if clone is initialized + @return true if in initial state */ + bool is_init() { return (m_clone_handle_state == CLONE_STATE_INIT); } + + /** Check if clone is idle waiting for restart + @return true if clone is in idle state */ + bool is_idle() { return (m_clone_handle_state == CLONE_STATE_IDLE); } + + /** Check if clone is aborted + @return true if clone is aborted */ + bool is_abort() { return (m_clone_handle_state == CLONE_STATE_ABORT); } + + /** Restart copy after a network failure + @param[in] thd server THD object + @param[in] loc locator with copy state from remote client + @param[in] loc_len locator length in bytes + @return error code */ + int restart_copy(THD *thd, const byte *loc, uint loc_len); + + /** Build locator with current state and restart apply + @param[in] thd server THD object + @param[in,out] loc loctor with current state information + @param[in,out] loc_len locator length in bytes + @return error code */ + int restart_apply(THD *thd, const byte *&loc, uint &loc_len); + + /** Transfer snapshot data via callback + @param[in] task_id current task ID + @param[in] callback user callback interface + @param[in] post_snapshot if called after snapshot stage + @return error code */ + int copy(uint task_id, Ha_clone_cbk *callback, bool post_snapshot); + + /** Takes SE snapshot: Finalizes the clone snapshot LSN. + @return error code */ + int snapshot(); + + /** Apply snapshot data received via callback + @param[in] thd server THD + @param[in] task_id current task ID + @param[in] callback user callback interface + @return error code */ + int apply(THD *thd, uint task_id, Ha_clone_cbk *callback); + + /** Send keep alive while during long wait + @param[in] task task that is sending the information + @param[in] callback callback interface + @return error code */ + int send_keep_alive(Clone_Task *task, Ha_clone_cbk *callback); + + /** @return true iff DDL should abort running clone. */ + bool abort_by_ddl() const { return m_abort_ddl; } + + /** Allow concurrent DDL to abort clone. */ + void set_ddl_abort() { m_abort_ddl = true; } + +#ifdef UNIV_DEBUG + /** Close master task file if open and unpin. */ + void close_master_file(); +#endif /* UNIV_DEBUG */ + + private: + /** Check if enough space is there to clone. + @param[in] task current task + @return error if not enough space */ + int check_space(const Clone_Task *task); + + /** Create clone data directory. + @return error code */ + int create_clone_directory(); + + /** Display clone progress + @param[in] cur_chunk current chunk number + @param[in] max_chunk total number of chunks + @param[in,out] percent_done percentage completed + @param[in,out] disp_time last displayed time */ + void display_progress(uint32_t cur_chunk, uint32_t max_chunk, + uint32_t &percent_done, + std::chrono::steady_clock::time_point &disp_time); + + /** Create a tablespace file and initialize. + @param[in] file_ctx file information + @param[in] file_type file type (data, log etc.) + @param[in] init true, if needs to write initial pages. + @return error code */ + int file_create_init(const Clone_file_ctx *file_ctx, ulint file_type, + bool init); + + using File_init_cbk = std::function; + + /** Open file for the task + @param[in] task clone task + @param[in] file_ctx file information + @param[in] file_type file type (data, log etc.) + @param[in] create_file create if not present + @param[in] init_cbk callback to fill initial data + @return error code */ + int open_file(Clone_Task *task, const Clone_file_ctx *file_ctx, + ulint file_type, bool create_file, File_init_cbk &init_cbk); + + /** Close file for the task + @param[in] task clone task + @return error code */ + int close_file(Clone_Task *task); + + /** Check and pin a file context if not already pinned. + @param[in,out] task clone task + @param[in,out] file_ctx snapshot file context + @param[out] handle_deleted true, iff caller needs to handle + deleted file state + @return error code */ + int check_and_pin_file(Clone_Task *task, Clone_file_ctx *file_ctx, + bool &handle_deleted); + + /** Unpin and close currently pinned file. + @param[in,out] task clone task + @return error code */ + int close_and_unpin_file(Clone_Task *task); + + /** Check if the task pins a file context. + @param[in] task clone task + @param[in] file_ctx snapshot file context + @return true, if task pins the file, other file. */ + std::tuple pins_file(const Clone_Task *task, + const Clone_file_ctx *file_ctx); + + /** Callback providing the file reference and data length to copy + @param[in] cbk callback interface + @param[in] task clone task + @param[in] len data length + @param[in] buf_cbk invoke buffer callback + @param[in] offset file offset + @param[in] src_file file name where func invoked + @param[in] src_line line where the func invoked + @return error code */ + int file_callback(Ha_clone_cbk *cbk, Clone_Task *task, uint len, bool buf_cbk, + uint64_t offset +#ifdef UNIV_PFS_IO + , + const char *src_file, uint src_line +#endif /* UNIV_PFS_IO */ + ); + + /** Move to next state + @param[in] task clone task + @param[in] callback callback interface + @param[in] state_desc descriptor for next state to move to + @return error code */ + int move_to_next_state(Clone_Task *task, Ha_clone_cbk *callback, + Clone_Desc_State *state_desc); + + /** Send current state information via callback + @param[in] task task that is sending the information + @param[in] callback callback interface + @param[in] is_start if it is the start of current state + @return error code */ + int send_state_metadata(Clone_Task *task, Ha_clone_cbk *callback, + bool is_start); + + /** Send current task information via callback + @param[in] task task that is sending the information + @param[in] callback callback interface + @return error code */ + int send_task_metadata(Clone_Task *task, Ha_clone_cbk *callback); + + /** Send all DDL metadata generated. + @param[in] task task that is sending the information + @param[in] callback callback interface + @return error code */ + int send_all_ddl_metadata(Clone_Task *task, Ha_clone_cbk *callback); + + /** Send all file information via callback + @param[in] task task that is sending the information + @param[in] callback callback interface + @return error code */ + int send_all_file_metadata(Clone_Task *task, Ha_clone_cbk *callback); + + /** Send current file information via callback + @param[in] task task that is sending the information + @param[in] file_meta file meta information + @param[in] is_redo true if redo file + @param[in] callback callback interface + @return error code */ + int send_file_metadata(Clone_Task *task, const Clone_File_Meta *file_meta, + bool is_redo, Ha_clone_cbk *callback); + + /** Send cloned data via callback + @param[in] task task that is sending the information + @param[in] file_ctx file information + @param[in] offset file offset + @param[in] buffer data buffer or NULL if send from file + @param[in] size data buffer size + @param[in] new_file_size updated file size from page 0 + @param[in] callback callback interface + @return error code */ + int send_data(Clone_Task *task, const Clone_file_ctx *file_ctx, + uint64_t offset, byte *buffer, uint32_t size, + uint64_t new_file_size, Ha_clone_cbk *callback); + + /** Process a data chunk and send data blocks via callback + @param[in] task task that is sending the information + @param[in] chunk_num chunk number to process + @param[in] block_num start block number + @param[in] callback callback interface + @return error code */ + int process_chunk(Clone_Task *task, uint32_t chunk_num, uint32_t block_num, + Ha_clone_cbk *callback); + + /** Create apply task based on task metadata in callback + @param[in] task current task + @param[in] callback callback interface + @return error code */ + int apply_task_metadata(Clone_Task *task, Ha_clone_cbk *callback); + + /** Move to next state based on state metadata and set + state information + @param[in] task current task + @param[in,out] callback callback interface + @param[in,out] state_desc clone state descriptor + @return error code */ + int ack_state_metadata(Clone_Task *task, Ha_clone_cbk *callback, + Clone_Desc_State *state_desc); + + /** Notify state change via callback. + @param[in] task current task + @param[in,out] callback callback interface + @param[in,out] state_desc clone state descriptor */ + void notify_state_change(Clone_Task *task, Ha_clone_cbk *callback, + Clone_Desc_State *state_desc); + + /** Move to next state based on state metadata and set + state information + @param[in] task current task + @param[in] callback callback interface + @return error code */ + int apply_state_metadata(Clone_Task *task, Ha_clone_cbk *callback); + + /** Create file metadata based on callback + @param[in] task current task + @param[in] callback callback interface + @return error code */ + int apply_file_metadata(Clone_Task *task, Ha_clone_cbk *callback); + + /** Apply DDL delete to existing file to update chunk and block information. + @param[in,out] task task performing the operation + @param[in,out] file_ctx current file context + @param[in] new_meta new file metadata + @return error code */ + int apply_file_delete(Clone_Task *task, Clone_file_ctx *file_ctx, + const Clone_File_Meta *new_meta); + + /** Apply DDL changes to file at the end of FILE_COPY stage. + @param[in] new_meta new file metadata + @param[in,out] file_ctx current file context + @return error code. */ + int apply_ddl(const Clone_File_Meta *new_meta, Clone_file_ctx *file_ctx); + + /** Set compression type based on local capability. + @param[in,out] file_ctx file context + @return error code. */ + int set_compression(Clone_file_ctx *file_ctx); + + /** Fix the file name and meta information for all files that are renamed + with DDL extension. + @param[in] task current task + @return error code. */ + int fix_all_renamed(const Clone_Task *task); + + /** Apply data received via callback + @param[in] task current task + @param[in] callback callback interface + @return error code */ + int apply_data(Clone_Task *task, Ha_clone_cbk *callback); + + /** Receive data from callback and apply + @param[in] task task that is receiving the information + @param[in] offset file offset for applying data + @param[in] file_size updated file size + @param[in] size data length in bytes + @param[in] callback callback interface + @return error code */ + int receive_data(Clone_Task *task, uint64_t offset, uint64_t file_size, + uint32_t size, Ha_clone_cbk *callback); + + /** Read compressed length from the page + @param[in] buffer data buffer + @param[in] len buffer length + @param[in] crc32 if full_crc32 is used + @param[in] block_size block size + @param[out] compressed_len compressed length + @return true for compressed page false otherwise. */ + bool read_compressed_len(unsigned char *buffer, uint32_t len, bool crc32, + uint32_t block_size, uint32_t &compressed_len); + + /** Write pages to file and punch holes + @param[in] file_meta clone file metadata + @param[in] buffer data buffer + @param[in] len buffer length + @param[in] file file descriptor + @param[in] start_off starting offset in file + @return error code */ + int sparse_file_write(Clone_File_Meta *file_meta, unsigned char *buffer, + uint32_t len, pfs_os_file_t file, uint64_t start_off); + + /** Modify page encryption attribute and/or punch hole. + @param[in] task task that is applying data + @param[in] offset file offset for applying data + @param[in,out] buffer data to apply + @param[in] buf_len data buffer length + @return error code */ + int modify_and_write(const Clone_Task *task, uint64_t offset, + unsigned char *buffer, uint32_t buf_len); + + private: + /** Clone handle type: Copy, Apply */ + Clone_Handle_Type m_clone_handle_type; + + /** Clone handle state */ + Clone_Handle_State m_clone_handle_state; + + /** Fixed locator for version negotiation. */ + byte m_version_locator[CLONE_DESC_MAX_BASE_LEN]; + + /** Serialized locator */ + byte *m_clone_locator; + + /** Locator length in bytes */ + uint m_locator_length; + + /** Serialized Restart locator */ + byte *m_restart_loc; + + /** Restart locator length in bytes */ + uint m_restart_loc_len; + + /** Clone descriptor version in use */ + uint m_clone_desc_version; + + /** Index in global array */ + uint m_clone_arr_index; + + /** Unique clone identifier */ + uint64_t m_clone_id; + + /** Reference count */ + uint m_ref_count; + + /** Allow restart of clone operation after network failure */ + bool m_allow_restart; + + /** If concurrent DDL should abort clone. */ + bool m_abort_ddl; + + /** Clone data directory */ + const char *m_clone_dir; + + /** Clone task manager */ + Clone_Task_Manager m_clone_task_manager; +}; + +/** Clone System */ +class Clone_Sys { + public: + /** RAII style wrapper to enter and exit wait stage. */ + class Wait_stage : private ib::Non_copyable { + public: + /** Constructor to change the THD information string. + @param[in] new_info new information string */ + explicit Wait_stage(const char *new_info); + + /** Destructor to revert back the old information string. */ + ~Wait_stage(); + + private: + /** Saved old THD information string. */ + const char *m_saved_info; + }; + + class Acquire_clone : private ib::Non_copyable { + public: + /** Constructor to get and pin clone handle. */ + explicit Acquire_clone(); + + /** Destructor to release and free clone handle if necessary. */ + ~Acquire_clone(); + + /** Get current clone snapshot. */ + Clone_Snapshot *get_snapshot(); + + private: + /** Acquired clone handle */ + Clone_Handle *m_clone{}; + }; + + /** Construct clone system */ + Clone_Sys(); + + /** Destructor: Call during system shutdown */ + ~Clone_Sys(); + + /** Create and add a new clone handle to clone system + @param[in] loc locator + @param[in] hdl_type handle type + @param[out] clone_hdl clone handle + @return error code */ + int add_clone(const byte *loc, Clone_Handle_Type hdl_type, + Clone_Handle *&clone_hdl); + + /** drop a clone handle from clone system + @param[in] clone_handle Clone handle */ + void drop_clone(Clone_Handle *clone_handle); + + /** Find if a clone is already running for the reference locator + @param[in] ref_loc reference locator + @param[in] loc_len reference locator length + @param[in] hdl_type clone type + @return clone handle if found, NULL otherwise */ + Clone_Handle *find_clone(const byte *ref_loc, uint loc_len, + Clone_Handle_Type hdl_type); + + /** Get the clone handle from locator by index + @param[in] loc locator + @param[in] loc_len locator length in bytes + @return clone handle */ + Clone_Handle *get_clone_by_index(const byte *loc, uint loc_len); + + /** Get or create a snapshot for clone and attach + @param[in] hdl_type handle type + @param[in] clone_type clone type + @param[in] snapshot_id snapshot identifier + @param[in] is_pfs_monitor true, if needs PFS monitoring + @param[out] snapshot clone snapshot + @return error code */ + int attach_snapshot(Clone_Handle_Type hdl_type, Ha_clone_type clone_type, + uint64_t snapshot_id, bool is_pfs_monitor, + Clone_Snapshot *&snapshot); + + /** Detach clone handle from snapshot + @param[in] snapshot snapshot + @param[in] hdl_type handle type */ + void detach_snapshot(Clone_Snapshot *snapshot, Clone_Handle_Type hdl_type); + + /** Mark clone state to abort if no active clone. If force is set, + abort all active clones and set state to abort. + @param[in] force force active clones to abort + @return true if global state is set to abort successfully */ + bool mark_abort(bool force); + + /** Mark clone state to active if no other abort request */ + void mark_active(); + + /** Mark to indicate that new clone operations should wait. */ + void mark_wait(); + + /** Free the wait marker. */ + void mark_free(); + +#ifdef UNIV_DEBUG + /** Debug wait while starting clone and waiting for free marker. */ + void debug_wait_clone_begin(); + + /** Close donor master task file if open and unpin. */ + void close_donor_master_file(); +#endif /* UNIV_DEBUG */ + + /** Wait for marker to get freed. + @param[in,out] thd user session + @return, error if timeout */ + int wait_for_free(THD *thd); + + /** Begin restricted state during some critical ddl phase. + @param[in] type ddl notification type + @param[in] space tablespace ID for which notification is sent + @param[in] no_wait return with error if needs to wait + @param[in] check_intr check for interrupt during wait + @param[out] blocked_state blocked state when state change is blocked + @param[out] error mysql error code + @return true iff clone needs to wait for state change. */ + bool begin_ddl_state(Clone_notify::Type type, space_id_t space, bool no_wait, + bool check_intr, uint32_t &blocked_state, int &error); + + /** End restricted state during some critical ddl phase. + @param[in] type ddl notification type + @param[in] space tablespace ID for which notification is sent + @param[in] blocked_state blocked state when state change is blocked */ + void end_ddl_state(Clone_notify::Type type, space_id_t space, + uint32_t blocked_state); + + /** Get next unique ID + @return unique ID */ + uint64_t get_next_id(); + + /** Get clone sys mutex + @return clone system mutex */ + mysql_mutex_t *get_mutex() { return (&m_clone_sys_mutex); } + + /** Clone System state */ + static Clone_Sys_State s_clone_sys_state; + + /** Number of active abort requests */ + static uint s_clone_abort_count; + + /** Number of active wait requests */ + static uint s_clone_wait_count; + + /** Function to check wait condition + @param[in] is_alert print alert message + @param[out] result true, if condition is satisfied + @return error code */ + using Wait_Cond_Cbk_Func = std::function; + + /** Wait till the condition is satisfied or timeout. + @param[in] sleep_time sleep time in milliseconds + @param[in] timeout total time to wait in seconds + @param[in] alert_interval alert interval in seconds + @param[in] func callback function for condition check + @param[in] mutex release during sleep and re-acquire + @param[out] is_timeout true if timeout + @return error code returned by callback function. */ + static int wait(Clone_Msec sleep_time, Clone_Sec timeout, + Clone_Sec alert_interval, Wait_Cond_Cbk_Func &&func, + mysql_mutex_t *mutex, bool &is_timeout) { + int err = 0; + bool wait = true; + is_timeout = false; + + int loop_count = 0; + auto alert_count = static_cast(alert_interval / sleep_time); + auto total_count = static_cast(timeout / sleep_time); + + /* Call function once before waiting. */ + err = func(false, wait); + + /* Start with 1 ms sleep and increase up to target sleep time. */ + Clone_Msec cur_sleep_time{1}; + + while (!is_timeout && wait && err == 0) { + /* Release input mutex */ + if (mutex != nullptr) { + mysql_mutex_assert_owner(mutex); + mysql_mutex_unlock(mutex); + } + + /* Limit sleep time to what is passed by caller. */ + if (cur_sleep_time > sleep_time) { + cur_sleep_time = sleep_time; + } + + std::this_thread::sleep_for(cur_sleep_time); + + if (cur_sleep_time < sleep_time) { + /* Double sleep time in each iteration till we reach target. */ + cur_sleep_time *= 2; + } else { + /* Increment count once we have reached target sleep time. */ + ++loop_count; + } + + /* Acquire input mutex back */ + if (mutex != nullptr) { + mysql_mutex_lock(mutex); + } + + /* We have not yet reached the target sleep time. */ + if (loop_count == 0) { + err = func(false, wait); + continue; + } + + auto alert = (alert_count > 0) ? (loop_count % alert_count == 0) : true; + + err = func(alert, wait); + + is_timeout = (loop_count > total_count); + } + return (err); + } + + /** Wait till the condition is satisfied or default timeout. + @param[in] func callback function for condition check + @param[in] mutex release during sleep and re-acquire + @param[out] is_timeout true if timeout + @return error code returned by callback function. */ + static int wait_default(Wait_Cond_Cbk_Func &&func, mysql_mutex_t *mutex, + bool &is_timeout) { + return (wait(CLONE_DEF_SLEEP, Clone_Sec(CLONE_DEF_TIMEOUT), + CLONE_DEF_ALERT_INTERVAL, + std::forward(func), mutex, is_timeout)); + } + + /** Check if any active clone is running. + @param[in] print_alert print alert message + @return true, if concurrent clone in progress */ + bool check_active_clone(bool print_alert); + + /** Check if any active clone is running. + @return (true, handle) if concurrent clone in progress */ + std::tuple check_active_clone(); + + /** @return GTID persistor */ + // Clone_persist_gtid &get_gtid_persistor() { return (m_gtid_persister); } + + /** Remember that all innodb spaces are initialized after last startup. */ + void set_space_initialized() { m_space_initialized.store(true); } + + /** @return true if all innodb spaces are initialized. */ + bool is_space_initialized() const { return m_space_initialized.load(); } + + private: + /** Find free index to allocate new clone handle. + @param[in] hdl_type clone handle type + @param[out] free_index free index in array + @return error code */ + int find_free_index(Clone_Handle_Type hdl_type, uint &free_index); + + /** Handle restricted state during critical ddl phase. + @param[in] type ddl notification type + @param[in] space tablespace ID for which notification is sent + @param[in] begin true, if beginning state + false, if ending + @return true iff clone needs to wait for state change. */ + bool handle_ddl_state(Clone_notify::Type type, space_id_t space, bool begin); + + private: + /** Array of clone handles */ + Clone_Handle *m_clone_arr[CLONE_ARR_SIZE]; + + /** Number of copy clones */ + uint m_num_clones; + + /** Number of apply clones */ + uint m_num_apply_clones; + + /** Array of clone snapshots */ + Clone_Snapshot *m_snapshot_arr[SNAPSHOT_ARR_SIZE]; + + /** Number of copy snapshots */ + uint m_num_snapshots; + + /** Number of apply snapshots */ + uint m_num_apply_snapshots; + + /** Clone system mutex */ + mysql_mutex_t m_clone_sys_mutex; + + /** Clone unique ID generator */ + uint64_t m_clone_id_generator; + + /** If all innodb tablespaces are initialized. */ + std::atomic m_space_initialized; + + /** GTID persister */ + // Clone_persist_gtid m_gtid_persister; +}; + +/** Clone system global */ +extern Clone_Sys *clone_sys; + +#endif /* CLONE_CLONE_INCLUDE */ diff --git a/storage/innobase/include/clone0desc.h b/storage/innobase/include/clone0desc.h new file mode 100644 index 0000000000000..2a172328049a4 --- /dev/null +++ b/storage/innobase/include/clone0desc.h @@ -0,0 +1,650 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/clone0desc.h + Innodb clone descriptors + + *******************************************************/ + +#ifndef CLONE_DESC_INCLUDE +#define CLONE_DESC_INCLUDE + +#include "mem0mem.h" +#include "os0file.h" +#include "univ.i" +#include "fil0fil.h" + +/** Invalid locator ID. */ +const uint64_t CLONE_LOC_INVALID_ID = 0; + +/** Maximum base length for any serialized descriptor. This is only used for +optimal allocation and has no impact on version compatibility. */ +const uint32_t CLONE_DESC_MAX_BASE_LEN = + 64 + MY_AES_MAX_KEY_LENGTH + MY_AES_MAX_KEY_LENGTH; +/** Align by 4K for O_DIRECT */ +const uint32_t CLONE_ALIGN_DIRECT_IO = 4 * 1024; + +/** Maximum number of concurrent tasks for each clone */ +const int CLONE_MAX_TASKS = 128; + +using space_id_t = decltype(fil_space_t::id); + +/** Snapshot state transfer during clone. + +Clone Type: HA_CLONE_BLOCKING +@startuml + state CLONE_SNAPSHOT_INIT + state CLONE_SNAPSHOT_FILE_COPY + state CLONE_SNAPSHOT_DONE + + [*] -down-> CLONE_SNAPSHOT_INIT : Build snapshot + CLONE_SNAPSHOT_INIT -right-> CLONE_SNAPSHOT_FILE_COPY + CLONE_SNAPSHOT_FILE_COPY -right-> CLONE_SNAPSHOT_DONE + CLONE_SNAPSHOT_DONE -down-> [*] : Destroy snapshot +@enduml + +Clone Type: HA_CLONE_REDO +@startuml + state CLONE_SNAPSHOT_REDO_COPY + + [*] -down-> CLONE_SNAPSHOT_INIT : Build snapshot + CLONE_SNAPSHOT_INIT -right-> CLONE_SNAPSHOT_FILE_COPY : Start redo archiving + CLONE_SNAPSHOT_FILE_COPY -right-> CLONE_SNAPSHOT_REDO_COPY + CLONE_SNAPSHOT_REDO_COPY -right-> CLONE_SNAPSHOT_DONE + CLONE_SNAPSHOT_DONE -down-> [*] : Destroy snapshot +@enduml + +Clone Type: HA_CLONE_HYBRID +@startuml + state CLONE_SNAPSHOT_PAGE_COPY + + [*] -down-> CLONE_SNAPSHOT_INIT : Build snapshot + CLONE_SNAPSHOT_INIT -right-> CLONE_SNAPSHOT_FILE_COPY : Start page tracking + CLONE_SNAPSHOT_FILE_COPY -right-> CLONE_SNAPSHOT_PAGE_COPY : Start redo \ + archiving + CLONE_SNAPSHOT_PAGE_COPY -right-> CLONE_SNAPSHOT_REDO_COPY + CLONE_SNAPSHOT_REDO_COPY -right> CLONE_SNAPSHOT_DONE + CLONE_SNAPSHOT_DONE -down-> [*] : Destroy snapshot +@enduml + +Clone Type: HA_CLONE_PAGE: Not implemented +*/ +enum Snapshot_State : uint32_t { + /** Invalid state */ + CLONE_SNAPSHOT_NONE = 0, + + /** Initialize state when snapshot object is created */ + CLONE_SNAPSHOT_INIT, + + /** Snapshot state while transferring files. */ + CLONE_SNAPSHOT_FILE_COPY, + + /** Snapshot state while transferring pages. */ + CLONE_SNAPSHOT_PAGE_COPY, + + /** Snapshot state while transferring redo. */ + CLONE_SNAPSHOT_REDO_COPY, + + /** Snapshot state at end after finishing transfer. */ + CLONE_SNAPSHOT_DONE +}; + +/** Total number of data transfer stages in clone. */ +const size_t CLONE_MAX_TRANSFER_STAGES = 3; + +/** Choose lowest descriptor version between reference locator +and currently supported version. +@param[in] ref_loc reference locator +@return chosen version */ +uint choose_desc_version(const byte *ref_loc); + +/** Check if clone locator is valid +@param[in] desc_loc serialized descriptor +@param[in] desc_len descriptor length +@return true, if valid locator */ +bool clone_validate_locator(const byte *desc_loc, uint desc_len); + +/** Clone descriptors contain meta information needed for applying cloned data. +These are PODs with interface to serialize and deserialize them. */ +enum Clone_Desc_Type { + /** Logical pointer to identify a clone operation */ + CLONE_DESC_LOCATOR = 1, + + /** Metadata for a Task/Thread for clone operation */ + CLONE_DESC_TASK_METADATA, + + /** Information for snapshot state */ + CLONE_DESC_STATE, + + /** Metadata for a database file */ + CLONE_DESC_FILE_METADATA, + + /** Information for a data block */ + CLONE_DESC_DATA, + + /** Must be the last member */ + CLONE_DESC_MAX +}; + +/** Header common to all descriptors. */ +struct Clone_Desc_Header { + /** Descriptor version */ + uint m_version; + + /** Serialized length of descriptor in bytes */ + uint m_length; + + /** Descriptor type */ + Clone_Desc_Type m_type; + + /** Serialize the descriptor header: Caller must allocate + the serialized buffer. + @param[out] desc_hdr serialized header */ + void serialize(byte *desc_hdr); + + /** Deserialize the descriptor header. + @param[in] desc_hdr serialized header + @param[in] desc_len descriptor length + @return true, if successful. */ + bool deserialize(const byte *desc_hdr, uint desc_len); +}; + +/** Task information in clone operation. */ +struct Clone_Task_Meta { + /** Index in task array. */ + uint m_task_index; + + /** Current chunk number reserved by the task. */ + uint m_chunk_num; + + /** Current block number that is already transferred. */ + uint m_block_num; +}; + +/** Map for current block number for unfinished chunks. Used during +restart from incomplete clone operation. */ +using Chunk_Map = std::map; + +/** Bitmap for completed chunks in current state */ +class Chnunk_Bitmap { + public: + /** Construct bitmap */ + Chnunk_Bitmap() : m_bitmap(), m_size(), m_bits() {} + + /** Bitmap array index operator implementation */ + class Bitmap_Operator_Impl { + public: + /** Construct bitmap operator + @param[in] bitmap reference to bitmap buffer + @param[in] index array operation index */ + Bitmap_Operator_Impl(uint32_t *&bitmap, uint32_t index) + + : m_bitmap_ref(bitmap) { + /* BYTE position */ + auto byte_index = index >> 3; + ut_ad(byte_index == index / 8); + + /* MAP array position */ + m_map_index = byte_index >> 2; + ut_ad(m_map_index == byte_index / 4); + + /* BIT position */ + auto bit_pos = index & 31; + ut_ad(bit_pos == index % 32); + + m_bit_mask = 1 << bit_pos; + } + + /** Check value at specified index in BITMAP + @return true if the BIT is set */ + operator bool() const { + auto &val = m_bitmap_ref[m_map_index]; + + if ((val & m_bit_mask) == 0) { + return (false); + } + + return (true); + } + + /** Set BIT at specific index + @param[in] bit bit value to set */ + void operator=(bool bit) { + auto &val = m_bitmap_ref[m_map_index]; + + if (bit) { + val |= m_bit_mask; + } else { + val &= ~m_bit_mask; + } + } + + private: + /** Reference to BITMAP array */ + uint32_t *&m_bitmap_ref; + + /** Current array position */ + uint32_t m_map_index; + + /** Mask with current BIT set */ + uint32_t m_bit_mask; + }; + + /** Array index operator + @param[in] index bitmap array index + @return operator implementation object */ + Bitmap_Operator_Impl operator[](uint32_t index) { + /* Convert to zero based index */ + --index; + + ut_a(index < m_bits); + return (Bitmap_Operator_Impl(m_bitmap, index)); + } + + /** Reset bitmap with new size + @param[in] max_bits number of BITs to hold + @param[in] heap heap for allocating memory + @return old buffer pointer */ + uint32_t *reset(uint32_t max_bits, mem_heap_t *heap); + + /** Get minimum BIT position that is not set + @return BIT position */ + uint32_t get_min_unset_bit(); + + /** Get maximum BIT position that is not set + @return BIT position */ + uint32_t get_max_set_bit(); + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_chunk serialized chunk info + @param[in,out] len length of serialized descriptor */ + void serialize(byte *&desc_chunk, uint &len); + + /** Deserialize the descriptor. + @param[in] desc_chunk serialized chunk info + @param[in,out] len_left length left in bytes */ + void deserialize(const byte *desc_chunk, uint &len_left); + + /** Get the length of serialized data + @return length serialized chunk info */ + size_t get_serialized_length(); + + /** Maximum bit capacity + @return maximum number of BITs it can hold */ + size_t capacity() const { return (8 * size()); } + + /** Size of bitmap in bytes + @return BITMAP buffer size */ + size_t size() const { return (m_size * 4); } + + /** Size of bitmap in bits + @return number of BITs stored */ + uint32_t size_bits() const { return (m_bits); } + + private: + /** BITMAP buffer */ + uint32_t *m_bitmap; + + /** BITMAP buffer size: Number of 4 byte blocks */ + size_t m_size; + + /** Total number of BITs in the MAP */ + uint32_t m_bits; +}; + +/** Incomplete Chunk information */ +struct Chunk_Info { + /** Information about chunks completed */ + Chnunk_Bitmap m_reserved_chunks; + + /** Information about unfinished chunks */ + Chunk_Map m_incomplete_chunks; + + /** Chunks for current state */ + uint32_t m_total_chunks; + + /** Minimum chunk number that is not reserved yet */ + uint32_t m_min_unres_chunk; + + /** Maximum chunk number that is already reserved */ + uint32_t m_max_res_chunk; + + /** Initialize Chunk number ranges */ + void init_chunk_nums() { + m_min_unres_chunk = m_reserved_chunks.get_min_unset_bit(); + ut_ad(m_min_unres_chunk <= m_total_chunks + 1); + + m_max_res_chunk = m_reserved_chunks.get_max_set_bit(); + ut_ad(m_max_res_chunk <= m_total_chunks); + } + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_chunk serialized chunk info + @param[in,out] len length of serialized descriptor */ + void serialize(byte *desc_chunk, uint &len); + + /** Deserialize the descriptor. + @param[in] desc_chunk serialized chunk info + @param[in,out] len_left length left in bytes */ + void deserialize(const byte *desc_chunk, uint &len_left); + + /** Get the length of serialized data + @param[in] num_tasks number of tasks to include + @return length serialized chunk info */ + size_t get_serialized_length(uint32_t num_tasks); +}; + +/** CLONE_DESC_LOCATOR: Descriptor for a task for clone operation. +A task is used by exactly one thread */ +struct Clone_Desc_Locator { + /** Descriptor header */ + Clone_Desc_Header m_header; + + /** Unique identifier for a clone operation. */ + uint64_t m_clone_id; + + /** Unique identifier for a clone snapshot. */ + uint64_t m_snapshot_id; + + /** Index in clone array for fast reference. */ + uint32_t m_clone_index; + + /** Current snapshot State */ + Snapshot_State m_state; + + /** Sub-state information: metadata transferred */ + bool m_metadata_transferred; + + /** Initialize clone locator. + @param[in] id Clone identifier + @param[in] snap_id Snapshot identifier + @param[in] state snapshot state + @param[in] version Descriptor version + @param[in] index clone index */ + void init(uint64_t id, uint64_t snap_id, Snapshot_State state, uint version, + uint index); + + /** Check if the passed locator matches the current one. + @param[in] other_desc input locator descriptor + @return true if matches */ + bool match(Clone_Desc_Locator *other_desc); + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_loc serialized descriptor + @param[in,out] len length of serialized descriptor + @param[in] chunk_info chunk information to serialize + @param[in] heap heap for allocating memory */ + void serialize(byte *&desc_loc, uint &len, Chunk_Info *chunk_info, + mem_heap_t *heap); + + /** Deserialize the descriptor. + @param[in] desc_loc serialized locator + @param[in] desc_len locator length + @param[in,out] chunk_info chunk information */ + void deserialize(const byte *desc_loc, uint desc_len, Chunk_Info *chunk_info); +}; + +/** CLONE_DESC_TASK_METADATA: Descriptor for a task for clone operation. +A task is used by exactly one thread */ +struct Clone_Desc_Task_Meta { + /** Descriptor header */ + Clone_Desc_Header m_header; + + /** Task information */ + Clone_Task_Meta m_task_meta; + + /** Initialize header + @param[in] version descriptor version */ + void init_header(uint version); + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_task serialized descriptor + @param[in,out] len length of serialized descriptor + @param[in] heap heap for allocating memory */ + void serialize(byte *&desc_task, uint &len, mem_heap_t *heap); + + /** Deserialize the descriptor. + @param[in] desc_task serialized descriptor + @param[in] desc_len descriptor length + @return true, if successful. */ + bool deserialize(const byte *desc_task, uint desc_len); +}; + +/** CLONE_DESC_STATE: Descriptor for current snapshot state */ +struct Clone_Desc_State { + /** Descriptor header */ + Clone_Desc_Header m_header; + + /** Current snapshot State */ + Snapshot_State m_state; + + /** Task identifier */ + uint m_task_index; + + /** Number of chunks in current state */ + uint m_num_chunks; + + /** Number of files in current state */ + uint m_num_files; + + /** Number of estimated bytes to transfer */ + uint64_t m_estimate; + + /** Number of estimated bytes on disk */ + uint64_t m_estimate_disk; + + /** If start processing state */ + bool m_is_start; + + /** State transfer Acknowledgement */ + bool m_is_ack; + + /** Initialize header + @param[in] version descriptor version */ + void init_header(uint version); + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_state serialized descriptor + @param[in,out] len length of serialized descriptor + @param[in] heap heap for allocating memory */ + void serialize(byte *&desc_state, uint &len, mem_heap_t *heap); + + /** Deserialize the descriptor. + @param[in] desc_state serialized descriptor + @param[in] desc_len descriptor length + @return true, if successful. */ + bool deserialize(const byte *desc_state, uint desc_len); +}; + +/** Clone file information */ +struct Clone_File_Meta { + /** Set file as deleted chunk. + @param[in] chunk chunk number that is found deleted. */ + inline void set_deleted_chunk(uint32_t chunk) { + m_begin_chunk = chunk; + m_end_chunk = 0; + m_deleted = true; + } + + /** @return true, iff file is deleted. */ + bool is_deleted() const { return m_deleted; } + + /** @return true, iff file is deleted. */ + bool is_renamed() const { return m_renamed; } + + /** @return true, iff file pages are encrypted. */ + bool can_encrypt() const { return m_is_encrypted; } + + /** @return true, iff file pages are compressed. */ + bool can_compress() const { return m_is_compressed; } + // bool can_encrypt() const { return m_encryption_metadata.can_encrypt(); } + + /** Reset DDL state of file metadata. */ + void reset_ddl() { + m_renamed = false; + m_deleted = false; + } + + /* Initialize parameters. */ + void init(); + + /** File size in bytes */ + uint64_t m_file_size; + + /** File allocation size on disk for sparse files. */ + uint64_t m_alloc_size; + + /** Tablespace FSP flags */ + uint32_t m_fsp_flags; + + /** Page compression is enabled. */ + bool m_is_compressed; + + /** Page encryption is enabled. */ + bool m_is_encrypted; + + /** If transparent compression is needed. It is derived information + and is not transferred. */ + bool m_punch_hole; + + /* Set file metadata as deleted. */ + bool m_deleted; + + /* Set file metadata as renamed. */ + bool m_renamed; + + /* Contains encryption key to be transferred. */ + bool m_transfer_encryption_key; + + /** File system block size. */ + size_t m_fsblk_size; + + /** Tablespace ID for the file */ + space_id_t m_space_id; + + /** File index in clone data file vector */ + uint m_file_index; + + /** Chunk number for the first chunk in file */ + uint m_begin_chunk; + + /** Chunk number for the last chunk in file */ + uint m_end_chunk; + + /** File name length in bytes */ + size_t m_file_name_len; + + /** Allocation length of name buffer. */ + size_t m_file_name_alloc_len; + + /** File name */ + const char *m_file_name; + + /** Encryption metadata: Since there is no master key, we should not + try to transfer encryption key which in not owned by SE. This would + require the cloned server to access the same key store. The other + solution is to decrypt and re-encrypt the whole dataset which would be + expensive. */ + // Encryption_metadata m_encryption_metadata; +}; + +/** CLONE_DESC_FILE_METADATA: Descriptor for file metadata */ +struct Clone_Desc_File_MetaData { + /** Descriptor header */ + Clone_Desc_Header m_header; + + /** Current snapshot State */ + Snapshot_State m_state; + + /** File metadata */ + Clone_File_Meta m_file_meta; + + /** Initialize header + @param[in] version descriptor version */ + void init_header(uint version); + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_file serialized descriptor + @param[in,out] len length of serialized descriptor + @param[in] heap heap for allocating memory */ + void serialize(byte *&desc_file, uint &len, mem_heap_t *heap); + + /** Deserialize the descriptor. + @param[in] desc_file serialized descriptor + @param[in] desc_len descriptor length + @return true, if successful. */ + bool deserialize(const byte *desc_file, uint desc_len); +}; + +/** CLONE_DESC_DATA: Descriptor for data */ +struct Clone_Desc_Data { + /** Descriptor header */ + Clone_Desc_Header m_header; + + /** Current snapshot State */ + Snapshot_State m_state; + + /** Task information */ + Clone_Task_Meta m_task_meta; + + /** File identifier */ + uint32_t m_file_index; + + /** Data Length */ + uint32_t m_data_len; + + /** File offset for the data */ + uint64_t m_file_offset; + + /** Updated file size */ + uint64_t m_file_size; + + /** Initialize header + @param[in] version descriptor version */ + void init_header(uint version); + + /** Serialize the descriptor. Caller should pass + the length if allocated. + @param[out] desc_data serialized descriptor + @param[in,out] len length of serialized descriptor + @param[in] heap heap for allocating memory */ + void serialize(byte *&desc_data, uint &len, mem_heap_t *heap); + + /** Deserialize the descriptor. + @param[in] desc_data serialized descriptor + @param[in] desc_len descriptor length + @return true, if successful. */ + bool deserialize(const byte *desc_data, uint desc_len); +}; + +#endif /* CLONE_DESC_INCLUDE */ diff --git a/storage/innobase/include/clone0monitor.h b/storage/innobase/include/clone0monitor.h new file mode 100644 index 0000000000000..b73e9855c3e09 --- /dev/null +++ b/storage/innobase/include/clone0monitor.h @@ -0,0 +1,208 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/clone0monitor.h + Performance Schema stage instrumentation to monitor clone progress. + + ****************************************************************************/ + +#ifndef CLONE_MONITOR_H +#define CLONE_MONITOR_H + +/* mysql_stage_inc_work_completed */ +#include "mysql/psi/mysql_stage.h" +#include "univ.i" +#include "ut0dbg.h" + +/** Function to alert caller for long wait. +@return error code */ +using Clone_Alert_Func = std::function; + +/** Class used to report CLONE progress via Performance Schema. */ +class Clone_Monitor { + public: + /** Constructor */ + Clone_Monitor() + : m_estimate(), + m_work_done(), + m_progress(), + m_estimate_bytes_left(), + m_work_bytes_left(), + m_cur_phase(NOT_STARTED) { + m_pfs_data_chunk_size = + static_cast(1 << PFS_DATA_CHUNK_SIZE_POW2); + } + + /** Destructor. */ + ~Clone_Monitor() { + if (m_progress == nullptr) { + return; + } + + mysql_end_stage(); + } + + /** Initialize all monitoring data. + @param[in] key PFS key to register stage event + @param[in] enable if true, enable PFS trackig. */ + void init_state(PSI_stage_key key, bool enable) { + change_phase(); + m_progress = nullptr; + m_estimate = 0; + m_work_done = 0; + m_estimate_bytes_left = 0; + m_work_bytes_left = 0; + + if (enable && key != PSI_NOT_INSTRUMENTED) { + m_progress = mysql_set_stage(key); + } + + if (m_progress == nullptr) { + m_cur_phase = NOT_STARTED; + return; + } + + m_cur_phase = ESTIMATE_WORK; + } + + /** @return true if in estimation phase */ + bool is_estimation_phase() const { return (m_cur_phase == ESTIMATE_WORK); } + + /** @return estimated work in bytes */ + uint64_t get_estimate() { + uint64_t ret_estimate = 0; + if (m_estimate > 0) { + ret_estimate = m_estimate << PFS_DATA_CHUNK_SIZE_POW2; + } + ret_estimate += m_estimate_bytes_left; + return (ret_estimate); + } + + /** Update the work estimated for the clone operation. + @param[in] size size in bytes that needs to transferred + across. */ + void add_estimate(uint64_t size) { + m_estimate += convert_bytes_to_work(size, true); + + if (m_cur_phase == NOT_STARTED) { + return; + } + + ut_ad(m_cur_phase == ESTIMATE_WORK); + ut_ad(m_progress != nullptr); + + mysql_stage_set_work_estimated(m_progress, m_estimate); + } + + /** Update the progress of the clone operation. + param[in] size size in bytes that is being transferred + across. */ + void update_work(uint size) { + if (m_cur_phase == NOT_STARTED) { + return; + } + + ut_ad(m_progress != nullptr); + ut_ad(m_cur_phase == COMPLETE_WORK); + + m_work_done += convert_bytes_to_work(size, false); + mysql_stage_set_work_completed(m_progress, m_work_done); + } + + /** Change from one phase to the other. */ + void change_phase() { + switch (m_cur_phase) { + case NOT_STARTED: + return; + + case ESTIMATE_WORK: + if (m_estimate_bytes_left != 0) { + mysql_stage_set_work_estimated(m_progress, m_estimate + 1); + } + + m_cur_phase = COMPLETE_WORK; + break; + + case COMPLETE_WORK: + if (m_work_bytes_left != 0) { + uint64_t rounded_estimate = m_estimate; + if (m_estimate_bytes_left != 0) { + ++rounded_estimate; + } + if (m_work_done < rounded_estimate) { + m_work_done++; + } + mysql_stage_set_work_completed(m_progress, m_work_done); + } + + m_cur_phase = NOT_STARTED; + break; + } + } + + private: + /** Translate bytes to work unit. + @param[in] size size in bytes that needs to be converted to the + @param[in] is_estimate if called during estimation + corresponding work unit. + @return the number of PFS chunks that the size constitutes. */ + uint64_t convert_bytes_to_work(uint64_t size, bool is_estimate) { + auto &bytes_left = is_estimate ? m_estimate_bytes_left : m_work_bytes_left; + size += bytes_left; + + auto aligned_size = ut_uint64_align_down(size, m_pfs_data_chunk_size); + bytes_left = size - aligned_size; + + return (aligned_size >> PFS_DATA_CHUNK_SIZE_POW2); + } + + /* Number of PFS chunks which needs to be transferred across. */ + uint64_t m_estimate; + + /* Number of PFS chunks already transferred. */ + uint64_t m_work_done; + + /* Performance schema accounting object. */ + PSI_stage_progress *m_progress; + + /* Size in bytes which couldn't fit the chunk during estimation. */ + uint64_t m_estimate_bytes_left; + + /* Size in bytes which couldn't fit the chunk during transfer. */ + uint64_t m_work_bytes_left; + + /* Current phase. */ + enum { NOT_STARTED = 0, ESTIMATE_WORK, COMPLETE_WORK } m_cur_phase; + + /* PFS Chunk size in power of 2 in unit of bytes. */ + static const int PFS_DATA_CHUNK_SIZE_POW2 = 20; + + /* PFS chunk size. */ + uint m_pfs_data_chunk_size; +}; + +#endif /* CLONE_MONITOR_H */ diff --git a/storage/innobase/include/clone0snapshot.h b/storage/innobase/include/clone0snapshot.h new file mode 100644 index 0000000000000..e19166142293d --- /dev/null +++ b/storage/innobase/include/clone0snapshot.h @@ -0,0 +1,1050 @@ +/***************************************************************************** + +Copyright (c) 2017, 2024, Oracle and/or its affiliates. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +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, version 2.0, +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 St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +/** @file include/clone0snapshot.h + Database Physical Snapshot + + *******************************************************/ + +#ifndef CLONE_SNAPSHOT_INCLUDE +#define CLONE_SNAPSHOT_INCLUDE + +#include "univ.i" + +#include "arch0log.h" +#include "arch0page.h" +#include "clone0api.h" +#include "clone0desc.h" +#include "clone0monitor.h" +#include "fil0fil.h" +#include "handler.h" + +#include +#include +#include + +#define UNIV_PAGE_SIZE ((uint32_t)srv_page_size) + +struct Clone_file_ctx { + /** File state: + [CREATED] -------------> [DROPPING] --> [DROPPED] --> [DROPPED_HANDLED] + | ^ + | | + ----> [RENAMING] -> [RENAMED] + | | + <------------ + */ + enum class State { + /* Invalid state. */ + NONE, + /* File is being dropped. */ + DROPPING, + /* File is being renamed. */ + RENAMING, + /* Newly created file or pre-existing before clone. */ + CREATED, + /* File is renamed during clone. */ + RENAMED, + /* File is deleted during clone. */ + DROPPED, + /* File is deleted and chunk information is handled. */ + DROPPED_HANDLED + }; + + /** File extension to use with name. */ + enum class Extension { + /* No extension. */ + NONE, + /* Replace extension - clone file to be replaced during recovery. */ + REPLACE, + /* DDL extension - temporary extension used during rename. */ + DDL + }; + + /** Initialize file state. + @param[in] extn file name extension */ + void init(Extension extn) { + m_state.store(State::CREATED); + m_extension = extn; + + m_pin.store(0); + m_modified_ddl = false; + m_waiting = 0; + + m_next_state = CLONE_SNAPSHOT_NONE; + + m_meta.init(); + } + + /** Get file name with extension. + @param[out] name file name. */ + void get_file_name(std::string &name) const; + + /** Mark file added by DDL. + @param[in] next_state next snapshot state */ + void set_ddl(Snapshot_State next_state) { + m_modified_ddl = true; + m_next_state = next_state; + } + + /** @return true iff added or modified by ddl in previous state. + @param[in] state current snapshot state */ + bool by_ddl(Snapshot_State state) const { + return m_modified_ddl && (state <= m_next_state); + } + + /** Start waiting for DDL */ + void begin_wait() { ++m_waiting; } + + /** Finish waiting for DDL */ + void end_wait() { + ut_a(m_waiting > 0); + --m_waiting; + } + + /** @return true, iff there are waiting clone tasks. */ + bool is_waiting() const { return (m_waiting > 0); } + + /** Pin the file. */ + void pin() { ++m_pin; } + + /** Unpin the file. */ + void unpin() { + ut_a(m_pin > 0); + --m_pin; + } + + /** @return true, iff clone tasks are using the file. */ + bool is_pinned() const { return (m_pin.load() > 0); } + + /** @return true, iff DDL is modifying file. */ + bool modifying() const { + State state = m_state.load(); + return (state == State::RENAMING || state == State::DROPPING); + } + + /** @return true, iff DDL is deleting file. */ + bool deleting() const { + State state = m_state.load(); + return (state == State::DROPPING); + } + + /** @return true, iff file is already deleted. */ + bool deleted() const { + State state = m_state.load(); + return (state == State::DROPPED || state == State::DROPPED_HANDLED); + } + + /** @return true, iff file is already renamed. */ + bool renamed() const { + State state = m_state.load(); + return (state == State::RENAMED); + } + + /** @return file metadata. */ + Clone_File_Meta *get_file_meta() { return &m_meta; } + + /** @return file metadata for read. */ + const Clone_File_Meta *get_file_meta_read() const { return &m_meta; } + + /** File metadata state. Modified by DDL commands. Protected by snapshot + mutex. Atomic operation helps clone to skip mutex when no ddl. */ + std::atomic m_state; + + /** File name extension. */ + Extension m_extension; + + private: + /** Pin count incremented and decremented by clone tasks to synchronize with + concurrent DDL. Protected by snapshot mutex. */ + std::atomic m_pin; + + /** Waiting count incremented and decremented by clone tasks while waiting + DDL file operation in progress. Protected by snapshot mutex. */ + uint32_t m_waiting; + + /** true, if file created or modified after clone is started. */ + bool m_modified_ddl{false}; + + /** Next state when ddl last modified file. */ + Snapshot_State m_next_state{CLONE_SNAPSHOT_DONE}; + + /** File metadata. */ + Clone_File_Meta m_meta; +}; + +/** Vector type for storing clone files */ +using Clone_File_Vec = std::vector; + +/** Map type for mapping space ID to clone file index */ +using Clone_File_Map = std::map; + +/** Page identified by space and page number */ +struct Clone_Page { + /** Tablespace ID */ + uint32_t m_space_id; + + /** Page number within tablespace */ + uint32_t m_page_no; +}; + +/** Comparator for storing sorted page ID. */ +struct Less_Clone_Page { + /** Less than operator for page ID. + @param[in] page1 first page + @param[in] page2 second page + @return true, if page1 is less than page2 */ + inline bool operator()(const Clone_Page &page1, + const Clone_Page &page2) const { + if (page1.m_space_id < page2.m_space_id) { + return (true); + } + + if (page1.m_space_id == page2.m_space_id && + page1.m_page_no < page2.m_page_no) { + return (true); + } + return (false); + } +}; + +/** Vector type for storing clone page IDs */ +using Clone_Page_Vec = std::vector; + +/** Set for storing unique page IDs. */ +using Clone_Page_Set = std::set; + +/** Clone handle type */ +enum Clone_Handle_Type { + /** Clone Handle for COPY */ + CLONE_HDL_COPY = 1, + + /** Clone Handle for APPLY */ + CLONE_HDL_APPLY +}; + +/** Default chunk size in power of 2 in unit of pages. +Chunks are reserved by each thread for multi-threaded clone. For 16k page +size, chunk size is 64M. */ +const uint SNAPSHOT_DEF_CHUNK_SIZE_POW2 = 12; + +/** Default block size in power of 2 in unit of pages. +Data transfer callback is invoked once for each block. This is also +the maximum size of data that would be re-send if clone is stopped +and resumed. For 16k page size, block size is 1M. */ +const uint SNAPSHOT_DEF_BLOCK_SIZE_POW2 = 6; + +/** Maximum block size in power of 2 in unit of pages. +For 16k page size, maximum block size is 64M. */ +const uint SNAPSHOT_MAX_BLOCK_SIZE_POW2 = 12; + +/** Dynamic database snapshot: Holds metadata and handle to data */ +class Clone_Snapshot { + public: + /** RAII style guard for begin & end of snapshot state transition. */ + class State_transit { + public: + /** Constructor to begin state transition. + @param[in,out] snapshot Clone Snapshot + @param[in] new_state State to transit */ + explicit State_transit(Clone_Snapshot *snapshot, Snapshot_State new_state); + + /** Destructor to end state transition. */ + ~State_transit(); + + /** @return error code */ + int get_error() const { return m_error; } + + /** Disable copy construction */ + State_transit(State_transit const &) = delete; + + /** Disable assignment */ + State_transit &operator=(State_transit const &) = delete; + + private: + /** Clone Snapshot */ + Clone_Snapshot *m_snapshot; + + /** Saved error while beginning transition. */ + int m_error; + }; + + /** Construct snapshot + @param[in] hdl_type copy, apply + @param[in] clone_type clone type + @param[in] arr_idx index in global array + @param[in] snap_id unique snapshot ID */ + Clone_Snapshot(Clone_Handle_Type hdl_type, Ha_clone_type clone_type, + uint arr_idx, uint64_t snap_id); + + /** Release contexts and free heap */ + ~Clone_Snapshot(); + + /** DDL notification before the operation. + @param[in] type type of DDL notification + @param[in] space space ID for the ddl operation + @param[in] no_wait return with error if needs to wait + @param[in] check_intr check for interrupt during wait + @param[out] error mysql error code + @return true iff clone state change is blocked. */ + bool begin_ddl_state(Clone_notify::Type type, space_id_t space, bool no_wait, + bool check_intr, int &error); + + /** DDL notification after the operation. + @param[in] type type of DDL notification + @param[in] space space ID for the ddl operation */ + void end_ddl_state(Clone_notify::Type type, space_id_t space); + + /** Wait for concurrent DDL file operation and pin file. + @param[in,out] file_ctx file context + @param[out] handle_delete if caller needs to handle deleted state + @return mysql error code. */ + int pin_file(Clone_file_ctx *file_ctx, bool &handle_delete); + + /** Unpin a file. + @param[in,out] file_ctx file context */ + void unpin_file(Clone_file_ctx *file_ctx) { file_ctx->unpin(); } + + /** Check if DDL needs to block clone operation. + @param[in] file_ctx file context + @return true iff clone operation needs to be blocked. */ + bool blocks_clone(const Clone_file_ctx *file_ctx); + + /** @return estimated bytes on disk */ + uint64_t get_disk_estimate() const { return (m_data_bytes_disk); } + + /** Get unique snapshot identifier + @return snapshot ID */ + uint64_t get_id() { return (m_snapshot_id); } + + /** Get snapshot index in global array + @return array index */ + uint get_index() { return (m_snapshot_arr_idx); } + + /** Get performance schema accounting object used to monitor stage + progress. + @return PFS stage object */ + Clone_Monitor &get_clone_monitor() { return (m_monitor); } + + /** Get snapshot heap used for allocation during clone. + @return heap */ + mem_heap_t *lock_heap() { + mysql_mutex_lock(&m_snapshot_mutex); + return (m_snapshot_heap); + } + + /* Release snapshot heap */ + void release_heap(mem_heap_t *&heap) { + heap = nullptr; + mysql_mutex_unlock(&m_snapshot_mutex); + } + + /** Get snapshot state + @return state */ + Snapshot_State get_state() { return (m_snapshot_state); } + + /** Get the redo file size for the snapshot + @return redo file size */ + uint64_t get_redo_file_size() { return (m_redo_file_size); } + + /** Get total number of chunks for current state + @return number of data chunks */ + uint get_num_chunks() { return (m_num_current_chunks); } + + /** Get maximum file length seen till now + @return file name length */ + size_t get_max_file_name_length() { return (m_max_file_name_len); } + + /** Get maximum buffer size required for clone + @return maximum dynamic buffer */ + uint get_dyn_buffer_length() { + uint ret_len = 0; + + if (is_copy() && m_snapshot_type != HA_CLONE_BLOCKING) { + ret_len = static_cast(2 * UNIV_PAGE_SIZE); + } + + return (ret_len); + } + + using File_Cbk_Func = std::function; + + /** Iterate through all files in current state + @param[in] func callback function + @return error code */ + int iterate_files(File_Cbk_Func &&func); + + /** Iterate through all data files + @param[in] func callback function + @return error code */ + int iterate_data_files(File_Cbk_Func &&func); + + /** Iterate through all redo files + @param[in] func callback function + @return error code */ + int iterate_redo_files(File_Cbk_Func &&func); + + /** Fill state descriptor from snapshot + @param[in] do_estimate estimate data bytes to transfer + @param[out] state_desc snapshot state descriptor */ + void get_state_info(bool do_estimate, Clone_Desc_State *state_desc); + + /** Set state information during apply + @param[in] state_desc snapshot state descriptor */ + void set_state_info(Clone_Desc_State *state_desc); + + /** Get next state based on snapshot type + @return next state */ + Snapshot_State get_next_state(); + + /** Try to attach to snapshot + @param[in] hdl_type copy, apply + @param[in] pfs_monitor enable PFS monitoring + @return true if successfully attached */ + bool attach(Clone_Handle_Type hdl_type, bool pfs_monitor); + + /** Detach from snapshot. */ + void detach(); + + /** Set current snapshot aborted state. Used in error cases before exiting + clone to make sure any DDL notifier exits waiting. */ + void set_abort(); + + /** @return true, iff clone has aborted. */ + bool is_aborted() const; + + /** Start transition to new state + @param[in] state_desc descriptor for next state + @param[in] new_state state to move for apply + @param[in] temp_buffer buffer used for collecting page IDs + @param[in] temp_buffer_len buffer length + @param[in] cbk alter callback for long wait + @return error code */ + int change_state(Clone_Desc_State *state_desc, Snapshot_State new_state, + byte *temp_buffer, uint temp_buffer_len, + Clone_Alert_Func cbk); + + /** Add file metadata entry at destination + @param[in] file_meta file metadata from donor + @param[in] data_dir destination data directory + @param[in] desc_create create if doesn't exist + @param[out] desc_exists descriptor already exists + @param[out] file_ctx if there, set to current file context + @return error code */ + int get_file_from_desc(const Clone_File_Meta *file_meta, const char *data_dir, + bool desc_create, bool &desc_exists, + Clone_file_ctx *&file_ctx); + + /** Rename an existing file descriptor. + @param[in] file_meta renamed file metadata from donor + @param[in] data_dir destination data directory + @param[out] file_ctx if there, set to current file context + @return error code */ + int rename_desc(const Clone_File_Meta *file_meta, const char *data_dir, + Clone_file_ctx *&file_ctx); + + /** Fix files renamed with ddl extension. The file name is checked against + existing file and added to appropriate status file. + @param[in] data_dir destination data directory + @param[in,out] file_ctx Set to correct extension + @return error code */ + int fix_ddl_extension(const char *data_dir, Clone_file_ctx *file_ctx); + + /** Add file descriptor to file list + @param[in,out] file_ctx current file context + @param[in] ddl_create added by DDL concurrently + @return true, if it is the last file. */ + bool add_file_from_desc(Clone_file_ctx *&file_ctx, bool ddl_create); + + /** Extract file information from node and add to snapshot + @param[in] node file node + @param[in] by_ddl node is added concurrently by DDL + @return error code */ + dberr_t add_node(fil_node_t *node, bool by_ddl); + + /** Add page ID to to the set of pages in snapshot + @param[in] space_id page tablespace + @param[in] page_num page number within tablespace + @return error code */ + int add_page(uint32_t space_id, uint32_t page_num); + + /** Add redo file to snapshot + @param[in] file_name file name + @param[in] file_size file size in bytes + @param[in] file_offset start offset + @return error code. */ + int add_redo_file(char *file_name, uint64_t file_size, uint64_t file_offset); + + /** Get file metadata by index for current state + @param[in] index file index + @return file metadata entry */ + Clone_File_Meta *get_file_by_index(uint index); + + /** Get clone file context by index for current state + @param[in] index file index + @return file context */ + Clone_file_ctx *get_file_ctx_by_index(uint index); + + /** Get clone file context by chunk and block number. + @param[in] chunk_num chunk number + @param[in] block_num block number + @param[in] hint_index hint file index number to start search. + @return file context */ + Clone_file_ctx *get_file_ctx(uint32_t chunk_num, uint32_t block_num, + uint32_t hint_index); + + /** Get next block of data to transfer + @param[in] chunk_num current chunk + @param[in,out] block_num current/next block + @param[in,out] file_ctx current/next block file context + @param[out] data_offset block offset in file + @param[out] data_buf data buffer or NULL if transfer from file + @param[out] data_size size of data in bytes + @param[out] file_size updated file size if extended + @return error code */ + int get_next_block(uint chunk_num, uint &block_num, + const Clone_file_ctx *&file_ctx, uint64_t &data_offset, + byte *&data_buf, uint32_t &data_size, uint64_t &file_size); + + /** Update snapshot block size based on caller's buffer size + @param[in] buff_size buffer size for clone transfer */ + void update_block_size(uint buff_size); + + /** @return chunk size in bytes. */ + inline uint32_t get_chunk_size() const { + return static_cast(chunk_size() * UNIV_PAGE_SIZE); + } + + /** @return number of blocks per chunk for different states. */ + uint32_t get_blocks_per_chunk() const; + + /** Check if copy snapshot + @return true if snapshot is for copy */ + bool is_copy() const { return (m_snapshot_handle_type == CLONE_HDL_COPY); } + + /** Get file offset while applying data. Currently, we use a single + redo log file during apply and offset is adjusted here. + @param[in] index file index from donor + @param[in] offset file offset from donor + @return adjusted file offset to write to. */ + uint64_t get_apply_file_offset(uint32_t index, uint64_t offset); + + /** Update file size when file is extended during page copy + @param[in] file_index current file index + @param[in] file_size new file size */ + void update_file_size(uint32_t file_index, uint64_t file_size); + + /** @return maximum blocks to transfer with file pinned. */ + uint32_t get_max_blocks_pin() const; + + /** Skip all blocks belonging to currently deleted file context. + @param[in] chunk_num current chunk + @param[in,out] block_num current, next block */ + void skip_deleted_blocks(uint32_t chunk_num, uint32_t &block_num); + + /** Initialize snapshot state for redo copy + @return error code */ + int init_redo_copy(); + + private: + /** Allow DDL file operation after 64 pages. */ + const static uint32_t S_MAX_PAGES_PIN = 64; + + /** Allow DDL file operation after every block (1M data by default) */ + const static uint32_t S_MAX_BLOCKS_PIN = 1; + + /** File name allocation size base. */ + const static size_t S_FILE_NAME_BASE_LEN = 256; + + /** Various wait types related to snapshot state. */ + enum class Wait_type { + /* DDL- limited wait if clone is waiting for another DDL. */ + STATE_TRANSIT_WAIT, + /* DDL- Wait till snapshot state transition is over. */ + STATE_TRANSIT, + /* DDL- Wait till PAGE COPY state is over. */ + STATE_END_PAGE_COPY, + /* Clone - Wait till there are no blockers for state transition. */ + STATE_BLOCKER, + /*DDL - Wait till the waiting clone threads are active. This are + clone threads from last DDL and useful to prevent starvation. */ + DATA_FILE_WAIT, + /* DDL - Wait till all threads have closed active data files. */ + DATA_FILE_CLOSE, + /* Clone - Wait till DDL file operation is complete. */ + DDL_FILE_OPERATION + }; + +#ifdef UNIV_DEBUG + /** Debug sync Wait during state transition. */ + void debug_wait_state_transit(); +#endif /* UNIV_DEBUG */ + + /** Update deleted state of a file if not yet done. + @param[in,out] file_ctx file context + @return true, if updated state */ + bool update_deleted_state(Clone_file_ctx *file_ctx); + + /** Get clone data file context by chunk number. + @param[in] chunk_num chunk number + @param[in] hint_index hint file index number to start search. + @return file context */ + Clone_file_ctx *get_data_file_ctx(uint32_t chunk_num, uint32_t hint_index); + + /** Get clone page file context by chunk number and block number. + @param[in] chunk_num chunk number + @param[in] block_num block number + @return file context */ + Clone_file_ctx *get_page_file_ctx(uint32_t chunk_num, uint32_t block_num); + + /** Get clone redo file context by chunk number. + @param[in] chunk_num chunk number + @param[in] hint_index hint file index number to start search. + @return file context */ + Clone_file_ctx *get_redo_file_ctx(uint32_t chunk_num, uint32_t hint_index); + + /** Get wait information string based on wait type. + @param[in] wait_type wait type + @return wait information string. */ + const char *wait_string(Wait_type wait_type) const; + + /** Wait for various operations based on type. + @param[in] type wait type + @param[in] ctx file context when relevant + @param[in] no_wait return with error if needs to wait + @param[in] check_intr check for interrupt during wait + @return mysql error code. */ + int wait(Wait_type type, const Clone_file_ctx *ctx, bool no_wait, + bool check_intr); + + /** During wait get relevant message string for logging. + @param[in] wait_type wait type + @param[out] info notification to log while waiting + @param[out] error error message to log on timeout */ + void get_wait_mesg(Wait_type wait_type, std::string &info, + std::string &error); + + /** Block clone state transition. Clone must wait. + @param[in] type type of DDL notification + @param[in] space space ID for the ddl operation + @param[in] no_wait return with error if needs to wait + @param[in] check_intr check for interrupt during wait` + @param[out] error mysql error code + @return true iff clone state change is blocked. */ + bool block_state_change(Clone_notify::Type type, space_id_t space, + bool no_wait, bool check_intr, int &error); + + /** Unblock clone state transition. */ + void unblock_state_change(); + + /** Get next file state while being modified by ddl. + @param[in] type ddl notification type + @param[in] begin true, if DDL begin notification + false, if DDL end notification + @return target file state. */ + Clone_file_ctx::State get_target_file_state(Clone_notify::Type type, + bool begin); + + /** Handle files for DDL begin notification. + @param[in] type type of DDL notification + @param[in] space space ID for the ddl operation + @param[in] no_wait return with error if needs to wait + @param[in] check_intr check for interrupt during wait + @return mysql error code */ + int begin_ddl_file(Clone_notify::Type type, space_id_t space, bool no_wait, + bool check_intr); + + /** Handle files for DDL end notification. + @param[in] type type of DDL notification + @param[in] space space ID for the ddl operation */ + void end_ddl_file(Clone_notify::Type type, space_id_t space); + + /** Begin state transition before waiting for DDL. */ + void begin_transit_ddl_wait() { + mysql_mutex_assert_owner(&m_snapshot_mutex); + /* Update number of clones to transit to new state. Set this prior to + waiting for DDLs blocking state transfer. This would help a new DDL to + find if clone is blocked by other DDL before state transition. */ + m_num_clones_transit = m_num_clones; + } + + /** Begin state transition. + @param[in] new_state state to transit to */ + void begin_transit(Snapshot_State new_state) { + mysql_mutex_assert_owner(&m_snapshot_mutex); + m_snapshot_next_state = new_state; + /* Move to next state. This is ok as the snapshot + mutex is not released till transition is ended, This + could change later when we ideally should release + the snapshot mutex during transition. */ + m_snapshot_state = m_snapshot_next_state; + } + + /** End state transition. */ + void end_transit() { + mysql_mutex_assert_owner(&m_snapshot_mutex); + m_num_clones_transit = 0; + m_snapshot_next_state = CLONE_SNAPSHOT_NONE; + } + + /** Check if state transition is in progress + @return true during state transition */ + bool in_transit_state() const { + mysql_mutex_assert_owner(&m_snapshot_mutex); + return (m_snapshot_next_state != CLONE_SNAPSHOT_NONE); + } + + /** @return true, if waiting before starting transition. Generally the + case when some DDL blocks state transition. */ + bool in_transit_wait() const { + mysql_mutex_assert_owner(&m_snapshot_mutex); + return (!in_transit_state() && m_num_clones_transit != 0); + } + + /** Start redo archiving. + @return error code */ + int init_redo_archiving(); + + /** Initialize snapshot state for file copy + @param[in] new_state state to move for apply + @return error code */ + int init_file_copy(Snapshot_State new_state); + + /** Initialize disk byte estimate. */ + void init_disk_estimate() { + /* Initial size is set to the redo file size on disk. */ + log_sys.latch.wr_lock(SRW_LOCK_CALL); + /* TODO: Get physical capacity. */ + m_data_bytes_disk = log_sys.log_capacity; + log_sys.latch.wr_unlock(); + } + + /** Initialize snapshot state for page copy + @param[in] new_state state to move for apply + @param[in] page_buffer temporary buffer to copy page IDs + @param[in] page_buffer_len buffer length + @return error code */ + int init_page_copy(Snapshot_State new_state, byte *page_buffer, + uint page_buffer_len); + + /** Initialize state while applying cloned data + @param[in] state_desc snapshot state descriptor + @return error code */ + int init_apply_state(Clone_Desc_State *state_desc); + + /** Extend and flush files after copying data + @param[in] flush_redo if true flush redo, otherwise data + @return error code */ + int extend_and_flush_files(bool flush_redo); + + /** Create file descriptor and add to current file list + @param[in] data_dir destination data directory + @param[in] file_meta file metadata from donor + @param[in] is_ddl if ddl temporary file + @param[out] file_ctx file context + @return error code */ + int create_desc(const char *data_dir, const Clone_File_Meta *file_meta, + bool is_ddl, Clone_file_ctx *&file_ctx); + + /** Get file context for current chunk + @param[in] file_vector clone file vector + @param[in] chunk_num current chunk number + @param[in] start_index index for starting the search + @return file context */ + Clone_file_ctx *get_file(Clone_File_Vec &file_vector, uint32_t chunk_num, + uint32_t start_index); + + /** Get next page from buffer pool + @param[in] chunk_num current chunk + @param[in,out] block_num current, next block + @param[in,out] file_ctx current, next block file context + @param[out] data_offset offset in file + @param[out] data_buf page data + @param[out] data_size page data size + @param[out] file_size updated file size if extended + @return error code */ + int get_next_page(uint chunk_num, uint &block_num, + const Clone_file_ctx *&file_ctx, uint64_t &data_offset, + byte *&data_buf, uint32_t &data_size, uint64_t &file_size); + + /** Get page from buffer pool and make ready for write + @param[in] page_id page ID chunk + @param[in] page_size physical page size on disk + @param[in] file_ctx clone file context + @param[out] page_data data page + @param[out] data_size page size in bytes + @return error code */ + int get_page_for_write(const page_id_t &page_id, uint32_t page_size, + const Clone_file_ctx *file_ctx, byte *&page_data, + uint &data_size); + + /* Make page ready for flush by updating LSN anc checksum + @param[in] zip_size ROW_FORMAT=COMPRESSED page size, or 0 + @param[in,out] page_data data page + @param[in] full_crc32 whether to use full_crc32 algorithm */ + void page_update_for_flush(ulint zip_size, byte *&page_data, + bool full_crc32); + + /* Handle page compression and encryption. */ + void page_compress_encrypt( + const Clone_File_Meta *file_meta, byte *&page_data, uint32_t data_size, + ulint zip_size, bool full_crc32, bool compress, bool encrypt, + uint32_t page_no); + + /** Build file metadata entry + @param[in] file_name name of the file + @param[in] file_size file size in bytes + @param[in] file_offset start offset + @param[in] num_chunks total number of chunks in the file + @return file context */ + Clone_file_ctx *build_file(const char *file_name, uint64_t file_size, + uint64_t file_offset, uint &num_chunks); + + /** Allocate and set clone file name. + @param[in,out] file_meta file metadata + @param[in] file_name file name + @return true iff successful. */ + bool build_file_name(Clone_File_Meta *file_meta, const char *file_name); + + /** Add buffer pool dump file to the file list + @return error code */ + int add_buf_pool_file(); + + /** Add file to snapshot + @param[in] name file name + @param[in] size_bytes file size in bytes + @param[in] alloc_bytes allocation size on disk for sparse file + @param[in] node file node + @param[in] by_ddl node is added concurrently by DDL + @return error code. */ + int add_file(const char *name, uint64_t size_bytes, uint64_t alloc_bytes, + fil_node_t *node, bool by_ddl); + + /** Check if file context has been changed by ddl. + @param[in] node tablespace file node + @param[out] file_ctx file context if exists + @return true iff file is created or modified by DDL. */ + bool file_ctx_changed(const fil_node_t *node, Clone_file_ctx *&file_ctx); + + /** Get chunk size + @return chunk size in pages */ + inline uint32_t chunk_size() const { + auto size = static_cast(1 << m_chunk_size_pow2); + return size; + } + + /** Get block size for file copy + @return block size in pages */ + uint32_t block_size() { + ut_a(m_block_size_pow2 <= SNAPSHOT_MAX_BLOCK_SIZE_POW2); + auto size = static_cast(1 << m_block_size_pow2); + + return size; + } + + /** Get number of blocks per chunk for file copy + @return blocks per chunk */ + inline uint32_t blocks_per_chunk() const { + ut_a(m_block_size_pow2 <= m_chunk_size_pow2); + return (1 << (m_chunk_size_pow2 - m_block_size_pow2)); + } + + /** Update system file name from configuration. + @param[in] replace if replacing current data directory + @param[in] file_meta file descriptor + @param[in,out] file_name file name to update + @return error code */ + int update_sys_file_name(bool replace, const Clone_File_Meta *file_meta, + std::string &file_name); + + /** Build file name along with path for cloned data files. + @param[in] data_dir clone data directory + @param[in] file_desc file descriptor + @param[out] file_path built file path if returned 0 + @return error code (0 on success) */ + int build_file_path(const char *data_dir, const Clone_File_Meta *file_desc, + std::string &file_path); + + /** Build file context from file path. + @param[in] extn file extension type + @param[in] file_meta file descriptor + @param[in] file_path data file along with path + @param[out] file_ctx created file context + @return error code */ + int build_file_ctx(Clone_file_ctx::Extension extn, + const Clone_File_Meta *file_meta, + const std::string &file_path, Clone_file_ctx *&file_ctx); + + /** Check for existing file and if clone extension is needed. This function + has the side effect to add undo file indexes. + @param[in] replace if data directory is replaced + @param[in] undo_file if undo tablespace file + @param[in] redo_file if redo file + @param[in] data_file_index index of file + @param[in] data_file data file name + @param[out] extn file extension needs to be used + @return error code */ + int handle_existing_file(bool replace, bool undo_file, bool redo_file, + uint32_t data_file_index, + const std::string &data_file, + Clone_file_ctx::Extension &extn); + + /** @return number of data files to transfer. */ + inline size_t num_data_files() const { return m_data_file_vector.size(); } + + /** @return number of redo files to transfer. */ + inline size_t num_redo_files() const { return m_redo_file_vector.size(); } + + private: + /** @name Snapshot type and ID */ + + /** Snapshot handle type */ + Clone_Handle_Type m_snapshot_handle_type; + + /** Clone type */ + Ha_clone_type m_snapshot_type; + + /** Unique snapshot ID */ + uint64_t m_snapshot_id; + + /** Index in global snapshot array */ + uint m_snapshot_arr_idx; + + /** @name Snapshot State */ + + /** Mutex to handle access by concurrent clones */ + mutable mysql_mutex_t m_snapshot_mutex; + + /** Number of blockers for state change. Usually DDLs for short duration. */ + uint32_t m_num_blockers; + + /** Set to true only if clone is aborted after error. */ + bool m_aborted; + + /** Number of clones attached to this snapshot */ + uint m_num_clones; + + /** Number of clones in in state transition */ + uint m_num_clones_transit; + + /** Current state */ + Snapshot_State m_snapshot_state; + + /** Next state to move to. Set only during state transfer. */ + Snapshot_State m_snapshot_next_state; + + /** @name Snapshot data block */ + + /** Memory allocation heap */ + mem_heap_t *m_snapshot_heap; + + /** Chunk size in power of 2 */ + uint m_chunk_size_pow2; + + /** Block size in power of 2 */ + uint m_block_size_pow2; + + /** Number of chunks in current state */ + uint m_num_current_chunks; + + /** Maximum file name length observed till now. */ + size_t m_max_file_name_len; + + /** @name Snapshot file data */ + + /** All data files for transfer */ + Clone_File_Vec m_data_file_vector; + + /** Map space ID to file vector index */ + Clone_File_Map m_data_file_map; + + /** Total number of data chunks */ + uint m_num_data_chunks; + + /** Number of bytes on disk. */ + uint64_t m_data_bytes_disk; + + /** Index into m_data_file_vector for all undo files. */ + std::vector m_undo_file_indexes; + + /** @name Snapshot page data */ + + /** Page archiver client */ + Page_Arch_Client_Ctx m_page_ctx; + + /** Set of unique page IDs */ + Clone_Page_Set m_page_set; + + /** Sorted page IDs to transfer */ + Clone_Page_Vec m_page_vector; + + /** Number of pages to transfer */ + uint m_num_pages; + + /** Number of duplicate pages found */ + uint m_num_duplicate_pages; + + /** @name Snapshot redo data */ + + /** redo log archiver client */ + Log_Arch_Client_Ctx m_redo_ctx; + + /** All archived redo files to transfer */ + Clone_File_Vec m_redo_file_vector; + + /** Start offset in first redo file */ + uint64_t m_redo_start_offset; + + /** Redo header block */ + byte *m_redo_header; + + /** Redo header size */ + uint m_redo_header_size; + + /** Redo trailer block */ + byte *m_redo_trailer; + + /** Redo trailer size */ + uint m_redo_trailer_size; + + /** Redo trailer block offset */ + uint64_t m_redo_trailer_offset; + + /** Archived redo file size */ + uint64_t m_redo_file_size; + + /** Total number of redo data chunks */ + uint m_num_redo_chunks; + + /** Enable PFS monitoring */ + bool m_enable_pfs; + + /** Performance Schema accounting object to monitor stage progress */ + Clone_Monitor m_monitor; +}; + +#endif /* CLONE_SNAPSHOT_INCLUDE */ diff --git a/storage/innobase/include/db0err.h b/storage/innobase/include/db0err.h index 642a3d2dbe05a..e9ace36b273c6 100644 --- a/storage/innobase/include/db0err.h +++ b/storage/innobase/include/db0err.h @@ -155,6 +155,9 @@ enum dberr_t { DB_PAGE_CORRUPTED, /* Page read from tablespace is corrupted. */ + + DB_ABORT_INCOMPLETE_CLONE, /* Incomplete cloned directory */ + /* The following are partial failure codes */ DB_FAIL = 1000, DB_OVERFLOW, diff --git a/storage/innobase/include/dict0load.h b/storage/innobase/include/dict0load.h index 69ccd3f816c5d..9808502dc8581 100644 --- a/storage/innobase/include/dict0load.h +++ b/storage/innobase/include/dict0load.h @@ -40,6 +40,9 @@ Created 4/24/1996 Heikki Tuuri /** A stack of table names related through foreign key constraints */ typedef std::deque > dict_names_t; +/** Load all tablespaces for clone when DDLs are blocked. */ +void dict_load_spaces_no_ddl(); + /** Check MAX(SPACE) FROM SYS_TABLES and store it in fil_system. Open each data file if an encryption plugin has been loaded. diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index 34a4d5db184c2..d2d3f226a2e50 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -64,6 +64,25 @@ enum srv_linux_aio_t /** innodb_flush_method */ extern ulong srv_file_flush_method; +/** Iterate over the files in all the tablespaces. */ +class Fil_iterator { + public: + using Function = std::function; + + /** For each data file. + @param[in] f Callback */ + template + static dberr_t for_each_file(F &&f) { + return iterate([=](fil_node_t *file) { return (f(file)); }); + } + + /** Iterate through all persistent tablespace files + returning the nodes via callback function f. + @param[in] f Callback + @return any error returned by the callback function. */ + static dberr_t iterate(Function &&f); +}; + /** Undo tablespaces starts with space_id. */ extern uint32_t srv_undo_space_id_start; /** The number of UNDO tablespaces that are open and ready to use. */ @@ -732,6 +751,9 @@ struct fil_space_t final /** @return whether the compression enabled for the tablespace. */ bool is_compressed() const noexcept { return is_compressed(flags); } + /** @return whether encryption is enabled for the tablespace. */ + bool is_encrypted() const; + /** Get the compression algorithm for full crc32 format. @param flags contents of FSP_SPACE_FLAGS @return PAGE_COMPRESSED algorithm of full_crc32 tablespace diff --git a/storage/innobase/include/fsp0file.h b/storage/innobase/include/fsp0file.h index 92d077d633cb7..5c20c54f29d83 100644 --- a/storage/innobase/include/fsp0file.h +++ b/storage/innobase/include/fsp0file.h @@ -320,6 +320,9 @@ class Datafile { void set_flags(uint32_t flags) { m_flags = flags; } uint32_t param_size() const { return m_user_param_size; } + + /** @return file size in number of pages */ + uint32_t size() const { return m_size; } private: /** Free the filepath buffer. */ void free_filepath(); diff --git a/storage/innobase/include/log0log.h b/storage/innobase/include/log0log.h index bc034208063c2..14c20a9336a3d 100644 --- a/storage/innobase/include/log0log.h +++ b/storage/innobase/include/log0log.h @@ -165,6 +165,9 @@ struct log_t (used to be 2048 before FORMAT_10_8). */ static constexpr lsn_t FIRST_LSN= START_OFFSET; + /** Clone header string in redo log header creator field. */ + static constexpr const char CREATOR_CLONE[]= "MariaDB Clone "; + private: /** the least significant bit of the write_to_buf buffer */ static constexpr size_t WRITE_TO_BUF_SHIFT{34}; @@ -222,7 +225,7 @@ struct log_t size_t write_to_log; /** Last written LSN; protected by latch */ - lsn_t write_lsn; + Atomic_relaxed write_lsn; /** Buffer for writing data to ib_logfile0, or nullptr if is_mmap(). In write_buf(), buf and flush_buf may be swapped */ @@ -241,6 +244,10 @@ struct log_t /** latest completed checkpoint (protected by latch.wr_lock()) */ Atomic_relaxed last_checkpoint_lsn; + + /** LSN for last checkpoint record. */ + lsn_t last_checkpoint_end_lsn; + /** The log writer (protected by latch.wr_lock()) */ lsn_t (*writer)() noexcept; /** next checkpoint LSN (protected by latch.wr_lock()) */ @@ -409,7 +416,8 @@ struct log_t @param buf log header buffer @param lsn log sequence number corresponding to log_sys.START_OFFSET @param encrypted whether the log is encrypted */ - static void header_write(byte *buf, lsn_t lsn, bool encrypted) noexcept; + static void header_write(byte *buf, lsn_t lsn, bool encrypted, + bool is_clone= false) noexcept; /** @return an estimate of get_lsn(), using acquire-release ordering with write_buf() or persist(); @@ -556,6 +564,13 @@ struct log_t /** Create the log. */ void create(lsn_t lsn) noexcept; + + /** Get last redo block from redo buffer and end LSN. + @param last_lsn end lsn of last mtr + @param last_block last redo block + @param block_len length in bytes */ + void get_last_block(lsn_t &last_lsn, byte *last_block, + uint32_t block_len); }; /** Redo log system */ diff --git a/storage/innobase/include/log0recv.h b/storage/innobase/include/log0recv.h index fe34a99b27e31..98d84ff53f634 100644 --- a/storage/innobase/include/log0recv.h +++ b/storage/innobase/include/log0recv.h @@ -280,6 +280,9 @@ struct recv_sys_t /** The contents of the doublewrite buffer */ recv_dblwr_t dblwr; + /** Data directory has been recognized as cloned data directory. */ + bool is_cloned_db= false; + __attribute__((warn_unused_result)) inline dberr_t read(os_offset_t offset, span buf); inline size_t files_size(); diff --git a/storage/innobase/include/os0file.h b/storage/innobase/include/os0file.h index 2bc80abb56269..9f516c83b3d96 100644 --- a/storage/innobase/include/os0file.h +++ b/storage/innobase/include/os0file.h @@ -46,6 +46,12 @@ Created 10/21/1995 Heikki Tuuri #include #endif /* !_WIN32 */ +#include + +/** Prefix all files and directory created under data directory with special +string so that it never conflicts with schema directory. */ +#define OS_FILE_PREFIX "#" + /** The maximum size of a read or write request. According to Linux "man 2 read" and "man 2 write" this applies to @@ -152,6 +158,8 @@ static constexpr ulint OS_LOG_FILE = 101; #if defined _WIN32 || defined O_DIRECT static constexpr ulint OS_DATA_FILE_NO_O_DIRECT = 103; #endif +static constexpr ulint OS_CLONE_DATA_FILE = 104; +static constexpr ulint OS_CLONE_LOG_FILE = 105; /* @} */ /** Error codes from os_file_get_last_error @{ */ @@ -345,6 +353,21 @@ fail_if_exists arguments is true. bool os_file_create_directory(const char *pathname, bool fail_if_exists) noexcept; +/** Callback function type to be implemented by caller. It is called for each +entry in directory. +@param[in] path path to the file +@param[in] name name of the file */ +typedef std::function os_dir_cbk_t; + +/** This function scans the contents of a directory and invokes the callback +for each entry. +@param[in] path directory name as null-terminated string +@param[in] scan_cbk use callback to be called for each entry +@param[in] is_drop attempt to drop the directory after scan +@return true if call succeeds, false on error */ +bool os_file_scan_directory(const char *path, os_dir_cbk_t scan_cbk, + bool is_drop); + /** NOTE! Use the corresponding macro os_file_create_simple(), not directly this function! A simple function to open or create a file. @@ -449,6 +472,8 @@ bool os_file_close_func(os_file_t file); /* Keys to register InnoDB I/O with performance schema */ extern mysql_pfs_key_t innodb_data_file_key; extern mysql_pfs_key_t innodb_temp_file_key; +extern mysql_pfs_key_t innodb_arch_file_key; +extern mysql_pfs_key_t innodb_clone_file_key; /* Following four macros are instumentations to register various file I/O operations with performance schema. @@ -575,6 +600,10 @@ The wrapper functions have the prefix of "innodb_". */ # define os_file_flush(file) \ pfs_os_file_flush_func(file, __FILE__, __LINE__) +#define os_file_copy(src, src_offset, dest, dest_offset, size) \ + pfs_os_file_copy_func(src, src_offset, dest, dest_offset, size, \ + __FILE__, __LINE__) + # define os_file_rename(key, oldpath, newpath) \ pfs_os_file_rename_func(key, oldpath, newpath, __FILE__, __LINE__) @@ -750,6 +779,26 @@ pfs_os_file_flush_func( const char* src_file, uint src_line); +/** copy data from one file to another file. Data is read/written +at current file offset. +@param[in] src file handle to copy from +@param[in] src_offset offset to copy from +@param[in] dest file handle to copy to +@param[in] dest_offset offset to copy to +@param[in] size number of bytes to copy +@param[in] src_file file name where func invoked +@param[in] src_line line where the func invoked +@return DB_SUCCESS if successful */ +UNIV_INLINE +dberr_t +pfs_os_file_copy_func( + pfs_os_file_t src, + os_offset_t src_offset, + pfs_os_file_t dest, + os_offset_t dest_offset, + uint size, + const char* src_file, + uint src_line); /** NOTE! Please use the corresponding macro os_file_rename(), not directly this function! @@ -837,6 +886,9 @@ to original un-instrumented file I/O APIs */ # define os_file_flush(file) os_file_flush_func(file) +# define os_file_copy(src, src_offset, dest, dest_offset, size) \ + os_file_copy_func(src, src_offset, dest, dest_offset, size) + # define os_file_rename(key, oldpath, newpath) \ os_file_rename_func(oldpath, newpath) @@ -881,6 +933,16 @@ os_file_truncate( os_offset_t size, bool allow_shrink = false) noexcept; +/** Set read/write position of a file handle to specific offset. +@param[in] pathname file path +@param[in] file file handle +@param[in] offset read/write offset +@return true if success */ +bool os_file_seek( + const char *pathname, + os_file_t file, + os_offset_t offset); + /** NOTE! Use the corresponding macro os_file_flush(), not directly this function! Flushes the write buffers of a given file to the disk. @@ -951,6 +1013,22 @@ os_file_write_func( ulint n) MY_ATTRIBUTE((warn_unused_result)); +/** copy data from one file to another file. Data is read/written +at current file offset. +@param[in] src file handle to copy from +@param[in] src_offset offset to copy from +@param[in] dest file handle to copy to +@param[in] dest_offset offset to copy to +@param[in] size number of bytes to copy +@return DB_SUCCESS if successful */ +dberr_t +os_file_copy_func( + os_file_t src, + os_offset_t src_offset, + os_file_t dest, + os_offset_t dest_offset, + uint size); + /** Check the existence and type of the given file. @param[in] path pathname of the file @param[out] exists true if file exists @@ -1037,6 +1115,11 @@ void os_aio_refresh_stats() noexcept; no pending io operations. */ bool os_aio_all_slots_free() noexcept; +/** Get available free space on disk +@param[in] path pathname of a directory or file in disk +@param[out] free_space free space available in bytes +@return DB_SUCCESS if all OK */ +dberr_t os_get_free_space(const char *path, uint64_t &free_space); /** This function returns information about the specified file @param[in] path pathname of the file diff --git a/storage/innobase/include/os0file.inl b/storage/innobase/include/os0file.inl index e5b9cb3408f54..882474e530d83 100644 --- a/storage/innobase/include/os0file.inl +++ b/storage/innobase/include/os0file.inl @@ -306,6 +306,38 @@ pfs_os_file_flush_func( return(result); } +static inline dberr_t pfs_os_file_copy_func( + pfs_os_file_t src, + os_offset_t src_offset, + pfs_os_file_t dest, + os_offset_t dest_offset, + uint size, + const char* src_file, + uint src_line) +{ + dberr_t result; + + PSI_file_locker_state state_read; + PSI_file_locker_state state_write; + + struct PSI_file_locker *locker_read = nullptr; + struct PSI_file_locker *locker_write = nullptr; + + register_pfs_file_io_begin(&state_read, locker_read, src, size, PSI_FILE_READ, + src_file, src_line); + + register_pfs_file_io_begin(&state_write, locker_write, dest, size, + PSI_FILE_WRITE, src_file, src_line); + + result = + os_file_copy_func(src, src_offset, dest, dest_offset, size); + + register_pfs_file_io_end(locker_write, size); + register_pfs_file_io_end(locker_read, size); + + return (result); +} + /** NOTE! Please use the corresponding macro os_file_rename(), not directly this function! This is the performance schema instrumented wrapper function for diff --git a/storage/innobase/include/srv0mon.h b/storage/innobase/include/srv0mon.h index 502c69be9da95..871f32bef6519 100644 --- a/storage/innobase/include/srv0mon.h +++ b/storage/innobase/include/srv0mon.h @@ -365,6 +365,12 @@ enum monitor_id_t { MONITOR_ICP_OUT_OF_RANGE, MONITOR_ICP_MATCH, + MONITOR_MODULE_PAGE_TRACK, + MONITOR_PAGE_TRACK_RESETS, + MONITOR_PAGE_TRACK_PARTIAL_BLOCK_WRITES, + MONITOR_PAGE_TRACK_FULL_BLOCK_WRITES, + MONITOR_PAGE_TRACK_CHECKPOINT_PARTIAL_FLUSH_REQUEST, + /* This is used only for control system to turn on/off and reset all monitor counters */ MONITOR_ALL_COUNTER, diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h index 8cf3835cf1d4f..ab85c35cea677 100644 --- a/storage/innobase/include/srv0srv.h +++ b/storage/innobase/include/srv0srv.h @@ -392,6 +392,7 @@ extern mysql_pfs_key_t page_cleaner_thread_key; extern mysql_pfs_key_t page_encrypt_thread_key; extern mysql_pfs_key_t trx_rollback_clean_thread_key; extern mysql_pfs_key_t thread_pool_thread_key; +extern mysql_pfs_key_t archiver_thread_key; /* This macro register the current thread and its key with performance schema */ @@ -442,6 +443,15 @@ extern PSI_stage_info srv_stage_alter_table_read_pk_internal_sort; extern PSI_stage_info srv_stage_buffer_pool_load; #endif /* HAVE_PSI_STAGE_INTERFACE */ +/** Performance schema stage event for monitoring clone file copy progress. */ +extern PSI_stage_info srv_stage_clone_file_copy; + +/** Performance schema stage event for monitoring clone redo copy progress. */ +extern PSI_stage_info srv_stage_clone_redo_copy; + +/** Performance schema stage event for monitoring clone page copy progress. */ +extern PSI_stage_info srv_stage_clone_page_copy; + /** Alternatives for srv_force_recovery. Non-zero values are intended to help the user get a damaged database up so that he can dump intact tables and rows with SELECT INTO OUTFILE. The database must not otherwise diff --git a/storage/innobase/include/srv0start.h b/storage/innobase/include/srv0start.h index c18cf1ceb63d1..904525bdefb31 100644 --- a/storage/innobase/include/srv0start.h +++ b/storage/innobase/include/srv0start.h @@ -118,7 +118,7 @@ extern bool srv_undo_sources; /** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */ -extern enum srv_shutdown_t srv_shutdown_state; +extern std::atomic srv_shutdown_state; /** Files comprising the system tablespace */ extern pfs_os_file_t files[1000]; diff --git a/storage/innobase/include/univ.i b/storage/innobase/include/univ.i index 3d11dd5e81102..a2ae27ecd8bc4 100644 --- a/storage/innobase/include/univ.i +++ b/storage/innobase/include/univ.i @@ -496,3 +496,45 @@ extern mysql_pfs_key_t log_latch_key; extern mysql_pfs_key_t trx_rseg_latch_key; # endif /* UNIV_PFS_RWLOCK */ #endif /* HAVE_PSI_INTERFACE */ + +/* RAII guard for mysql mutex */ +struct Mysql_mutex_guard { + /** + Constructor to acquire mutex + @param in_mutex input mutex + */ + Mysql_mutex_guard(mysql_mutex_t *in_mutex) + : m_mutex(in_mutex) { mysql_mutex_lock(in_mutex); } + + /** Destructor to release mutex */ + ~Mysql_mutex_guard() { clear(); } + + /** Disable copy construction */ + Mysql_mutex_guard(Mysql_mutex_guard const &) = delete; + + /** Disable assignment */ + Mysql_mutex_guard &operator=(Mysql_mutex_guard const &) = delete; + + private: + /** Current mutex for RAII */ + mysql_mutex_t *m_mutex; + + void clear() { + mysql_mutex_unlock(m_mutex); + m_mutex = nullptr; + } +}; + +#ifdef _WIN32 +#define OS_PATH_SEPARATOR_STR "\\" +#define OS_PATH_SEPARATOR '\\' +#else +#define OS_PATH_SEPARATOR_STR "/" +#define OS_PATH_SEPARATOR '/' +#endif /* _WIN32 */ + +#ifdef UNIV_DEBUG +#define IF_DEBUG(...) __VA_ARGS__ +#else +#define IF_DEBUG(...) +#endif /* UNIV_DEBUG */ diff --git a/storage/innobase/include/ut0byte.h b/storage/innobase/include/ut0byte.h index 2b70fac3c96e7..09bd5b246ba36 100644 --- a/storage/innobase/include/ut0byte.h +++ b/storage/innobase/include/ut0byte.h @@ -60,6 +60,16 @@ ut_uint64_align_up( ib_uint64_t n, /*!< in: number to be rounded */ ulint align_no); /*!< in: align by this number which must be a power of 2 */ + +/** Rounds upward to a multiple of a power of 2 */ +static inline void *ut_align(const void *ptr, ulint align_no) { + ut_ad(align_no > 0); + ut_ad(((align_no - 1) & align_no) == 0); + ut_ad(ptr); + static_assert(sizeof(void *) == sizeof(ulint)); + return ((void *)((((ulint)ptr) + align_no - 1) & ~(align_no - 1))); +} + /** Round down a pointer to the nearest aligned address. @param ptr pointer @param alignment a power of 2 diff --git a/storage/innobase/include/ut0dbg.h b/storage/innobase/include/ut0dbg.h index 85856660494e8..31db4678fec72 100644 --- a/storage/innobase/include/ut0dbg.h +++ b/storage/innobase/include/ut0dbg.h @@ -33,10 +33,16 @@ Created 1/30/1994 Heikki Tuuri #define ut_error assert(0) #else /* !UNIV_INNOCHECKSUM */ +#include /* Do not include univ.i because univ.i includes this. */ /*************************************************************//** Report a failed assertion. */ + +/** Set a callback function to be called before exiting. +@param[in] callback user callback function */ +void ut_set_assert_callback(std::function &callback); + ATTRIBUTE_NORETURN ATTRIBUTE_COLD __attribute__((nonnull(2))) void ut_dbg_assertion_failed( diff --git a/storage/innobase/include/ut0new.h b/storage/innobase/include/ut0new.h index 398dd0dcc9ecd..8e9e6bd7d770c 100644 --- a/storage/innobase/include/ut0new.h +++ b/storage/innobase/include/ut0new.h @@ -165,7 +165,9 @@ ut_allocator::get_mem_key()): happens then that means that the list of predefined names must be extended. Keep this list alphabetically sorted. */ extern PSI_memory_key mem_key_ahi; +extern PSI_memory_key mem_key_archive; extern PSI_memory_key mem_key_buf_buf_pool; +extern PSI_memory_key mem_key_clone; extern PSI_memory_key mem_key_dict_stats_bg_recalc_pool_t; extern PSI_memory_key mem_key_dict_stats_index_map_t; extern PSI_memory_key mem_key_dict_stats_n_diff_on_level; @@ -831,6 +833,10 @@ static constexpr bool cexpr_strequal_ignore_dot(const char* a, const char* b) constexpr const char* const auto_event_names[] = { + "arch0arch", + "arch0page", + "arch0log", + "arch0recv", "btr0btr", "btr0buf", "btr0bulk", @@ -842,6 +848,12 @@ constexpr const char* const auto_event_names[] = "buf0dump", "buf0lru", "buf0rea", + "clone0api", + "clone0apply", + "clone0desc", + "clone0clone", + "clone0copy", + "clone0snapshot.cc", "dict0dict", "dict0mem", "dict0stats", diff --git a/storage/innobase/include/ut0ut.h b/storage/innobase/include/ut0ut.h index e3930fb3caf1d..3e8e1de16b42d 100644 --- a/storage/innobase/include/ut0ut.h +++ b/storage/innobase/include/ut0ut.h @@ -361,6 +361,18 @@ class fatal_or_error : public logger { const bool m_fatal; }; +/** A utility class which, if inherited from, prevents the descendant class +from being copied, moved, or assigned. This is useful for guard classes. */ +class Non_copyable { + public: + Non_copyable(const Non_copyable &) = delete; + Non_copyable &operator=(const Non_copyable &) = delete; + + protected: + Non_copyable() = default; + ~Non_copyable() = default; /// Protected non-virtual destructor +}; + } // namespace ib #endif diff --git a/storage/innobase/log/log0log.cc b/storage/innobase/log/log0log.cc index a91635e85d0f9..a62e0c60e5669 100644 --- a/storage/innobase/log/log0log.cc +++ b/storage/innobase/log/log0log.cc @@ -28,6 +28,7 @@ Created 12/9/1995 Heikki Tuuri #include #include +#include "arch0arch.h" #include "log0log.h" #include "log0crypt.h" #include "buf0buf.h" @@ -109,6 +110,7 @@ void log_t::create() noexcept #endif last_checkpoint_lsn= FIRST_LSN; + last_checkpoint_end_lsn= FIRST_LSN; log_capacity= 0; max_modified_age_async= 0; max_checkpoint_age= 0; @@ -402,7 +404,8 @@ bool log_t::attach(log_file_t file, os_offset_t size) noexcept @param buf log header buffer @param lsn log sequence number corresponding to log_sys.START_OFFSET @param encrypted whether the log is encrypted */ -void log_t::header_write(byte *buf, lsn_t lsn, bool encrypted) noexcept +void log_t::header_write(byte *buf, lsn_t lsn, bool encrypted, + bool is_clone) noexcept { mach_write_to_4(my_assume_aligned<4>(buf) + LOG_HEADER_FORMAT, log_sys.FORMAT_10_8); @@ -412,8 +415,10 @@ void log_t::header_write(byte *buf, lsn_t lsn, bool encrypted) noexcept # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wstringop-truncation" #endif + std::string clone_header(log_t::CREATOR_CLONE); + clone_header.append(PACKAGE_VERSION); strncpy(reinterpret_cast(buf) + LOG_HEADER_CREATOR, - "MariaDB " PACKAGE_VERSION, + is_clone ? clone_header.c_str() : "MariaDB " PACKAGE_VERSION, LOG_HEADER_CREATOR_END - LOG_HEADER_CREATOR); #if defined __GNUC__ && __GNUC__ > 7 # pragma GCC diagnostic pop @@ -438,6 +443,7 @@ void log_t::create(lsn_t lsn) noexcept write_lsn= lsn; last_checkpoint_lsn= 0; + last_checkpoint_end_lsn= 0; DBUG_PRINT("ib_log", ("write header " LSN_PF, lsn)); @@ -918,6 +924,9 @@ void log_t::persist(lsn_t lsn) noexcept ut_ad(!flush_lock.is_owner()); ut_ad(latch_have_wr()); + if (arch_sys) + arch_sys->log_sys()->wait_archiver(lsn); + lsn_t old= flushed_to_disk_lsn.load(std::memory_order_relaxed); if (old >= lsn) @@ -945,6 +954,8 @@ void log_t::persist(lsn_t lsn) noexcept base_lsn.store(new_base_lsn, std::memory_order_release); flushed_to_disk_lsn.store(lsn, std::memory_order_relaxed); log_flush_notify(lsn); + if (arch_sys) + arch_sys->signal_archiver(); DBUG_EXECUTE_IF("crash_after_log_write_upto", DBUG_SUICIDE();); } @@ -1092,12 +1103,14 @@ lsn_t log_t::write_buf() noexcept ut_ad(base + (write_lsn_offset & (WRITE_TO_BUF - 1)) == lsn); write_to_log++; + if (arch_sys) + arch_sys->log_sys()->wait_archiver(lsn); if (resizing != RETAIN_LATCH) latch.wr_unlock(); DBUG_PRINT("ib_log", ("write " LSN_PF " to " LSN_PF " at " LSN_PF, - write_lsn, lsn, offset)); + write_lsn.load(), lsn, offset)); /* Do the write to the log file */ log_write_buf(write_buf, length, offset); @@ -1109,8 +1122,10 @@ lsn_t log_t::write_buf() noexcept if (UNIV_UNLIKELY(srv_shutdown_state > SRV_SHUTDOWN_INITIATED)) { service_manager_extend_timeout(INNODB_EXTEND_TIMEOUT_INTERVAL, - "InnoDB log write: " LSN_PF, write_lsn); + "InnoDB log write: " LSN_PF, write_lsn.load()); } + if (arch_sys) + arch_sys->signal_archiver(); } set_check_for_checkpoint(false); @@ -1231,6 +1246,35 @@ void log_buffer_flush_to_disk(bool durable) noexcept log_write_up_to(log_get_lsn(), durable); } +void log_t::get_last_block(lsn_t &last_lsn, byte *last_block, + uint32_t block_len) +{ + ut_ad(ut_is_2pow(block_len)); + ut_ad(block_len <= write_size); + + latch.wr_lock(SRW_LOCK_CALL); + last_lsn= get_lsn(); + + lsn_t aligned_lsn= Arch_Group::align_lsn(last_lsn, get_first_lsn()); + lsn_t data_len= last_lsn - aligned_lsn; + lsn_t offset= 0; + + if (is_mmap()) + offset= log_sys.calc_lsn_offset(aligned_lsn); + else + { + lsn_t available_len= write_lsn_offset & (WRITE_BACKOFF - 1); + ut_ad(available_len >= data_len); + offset= available_len - data_len; + } + std::memcpy(last_block, buf + offset, + static_cast(data_len)); + + latch.wr_unlock(); + std::memset(last_block + data_len, 0x00, + static_cast(block_len - data_len)); +} + /** Prepare to invoke log_write_and_flush(), before acquiring log_sys.latch. */ ATTRIBUTE_COLD void log_write_and_flush_prepare() noexcept { diff --git a/storage/innobase/log/log0recv.cc b/storage/innobase/log/log0recv.cc index fa5791e7eb4e8..a50034297747a 100644 --- a/storage/innobase/log/log0recv.cc +++ b/storage/innobase/log/log0recv.cc @@ -1336,6 +1336,7 @@ void recv_sys_t::create() mysql_mutex_init(recv_sys_mutex_key, &mutex, nullptr); apply_log_recs = false; + is_cloned_db = false; len = 0; offset = 0; @@ -1747,13 +1748,16 @@ dberr_t recv_sys_t::find_checkpoint() { log_sys.next_checkpoint_lsn= checkpoint_lsn; log_sys.next_checkpoint_no= field == log_t::CHECKPOINT_1; - lsn= end_lsn; + log_sys.last_checkpoint_end_lsn= lsn= end_lsn; } } if (!log_sys.next_checkpoint_lsn) goto got_no_checkpoint; if (!memcmp(creator, "Backup ", 7)) srv_start_after_restore= true; + else if (!memcmp(creator, log_t::CREATOR_CLONE, + sizeof(log_t::CREATOR_CLONE) - 1)) + is_cloned_db= true; return DB_SUCCESS; case log_t::FORMAT_10_5: case log_t::FORMAT_10_5 | log_t::FORMAT_ENCRYPTED: @@ -2451,7 +2455,11 @@ recv_sys_t::parse_mtr_result recv_sys_t::parse(source &l, bool if_exists) return PREMATURE_EOF; eom_found: - if (*l != log_sys.get_sequence_bit((l - begin) + lsn)) + /* Cloned redo log file is not overwritten and we don't need sequence bit + check to detect the end. Also, the redo log could be cloned from a wrapped + around redo and the sequence BIT may not match. This is not an issue as the + sequence BIT doesn't have anything to do with the logged information. */ + if (!is_cloned_db && *l != log_sys.get_sequence_bit((l - begin) + lsn)) return GOT_EOF; if (l.is_eof(4)) @@ -3287,7 +3295,7 @@ static buf_block_t *recv_recover_page(buf_block_t *block, mtr_t &mtr, buf_pool_t::insert_into_flush_list() */ mysql_mutex_lock(&buf_pool.flush_list_mutex); buf_pool.flush_list_bytes+= block->physical_size(); - block->page.set_oldest_modification(start_lsn); + block->page.set_oldest_modification(start_lsn, true); UT_LIST_ADD_FIRST(buf_pool.flush_list, &block->page); buf_pool.page_cleaner_wakeup(); mysql_mutex_unlock(&buf_pool.flush_list_mutex); @@ -3995,7 +4003,7 @@ void recv_sys_t::apply(bool last_batch) buf_pool_invalidate(); log_sys.latch.wr_lock(SRW_LOCK_CALL); } - else if (srv_operation == SRV_OPERATION_RESTORE || + else if (is_cloned_db || srv_operation == SRV_OPERATION_RESTORE || srv_operation == SRV_OPERATION_RESTORE_EXPORT) buf_flush_sync_batch(lsn); else diff --git a/storage/innobase/mtr/mtr0mtr.cc b/storage/innobase/mtr/mtr0mtr.cc index 6d67cfa6b0734..2212fd9b52b92 100644 --- a/storage/innobase/mtr/mtr0mtr.cc +++ b/storage/innobase/mtr/mtr0mtr.cc @@ -142,10 +142,11 @@ inline buf_page_t *buf_pool_t::prepare_insert_into_flush_list(lsn_t lsn) /** Insert a modified block into the flush list. @param prev insert position (from prepare_insert_into_flush_list()) @param block modified block -@param lsn start LSN of the mini-transaction that modified the block */ +@param lsn start LSN of the mini-transaction that modified the block +@param mark_tracking mark the page for tracking */ inline void buf_pool_t::insert_into_flush_list(buf_page_t *prev, - buf_block_t *block, lsn_t lsn) - noexcept + buf_block_t *block, lsn_t lsn, + bool mark_tracking) noexcept { ut_ad(!fsp_is_system_temporary(block->page.id().space())); mysql_mutex_assert_owner(&flush_list_mutex); @@ -171,7 +172,7 @@ inline void buf_pool_t::insert_into_flush_list(buf_page_t *prev, else UT_LIST_ADD_FIRST(flush_list, &block->page); - block->page.set_oldest_modification(lsn); + block->page.set_oldest_modification(lsn, mark_tracking); } mtr_t::mtr_t()= default; @@ -262,7 +263,7 @@ static void insert_imported(buf_block_t *block) const lsn_t lsn= log_sys.get_lsn(); mysql_mutex_lock(&buf_pool.flush_list_mutex); buf_pool.insert_into_flush_list - (buf_pool.prepare_insert_into_flush_list(lsn), block, lsn); + (buf_pool.prepare_insert_into_flush_list(lsn), block, lsn, true); log_sys.latch.wr_unlock(); mysql_mutex_unlock(&buf_pool.flush_list_mutex); } @@ -361,13 +362,17 @@ void mtr_t::commit_log(mtr_t *mtr, std::pair lsns) ut_d(const auto s= b->page.state()); ut_ad(s > buf_page_t::FREED); ut_ad(s < buf_page_t::READ_FIX); - ut_ad(mach_read_from_8(b->page.frame + FIL_PAGE_LSN) <= - mtr->m_commit_lsn); + + lsn_t frame_lsn= mach_read_from_8(b->page.frame + FIL_PAGE_LSN); + ut_ad(frame_lsn <= mtr->m_commit_lsn); + auto [tracking, track_lsn]= buf_pool.is_tracking(); + bool track_mark= (tracking && frame_lsn <= track_lsn); + mach_write_to_8(b->page.frame + FIL_PAGE_LSN, mtr->m_commit_lsn); if (UNIV_LIKELY_NULL(b->page.zip.data)) memcpy_aligned<8>(FIL_PAGE_LSN + b->page.zip.data, FIL_PAGE_LSN + b->page.frame, 8); - buf_pool.insert_into_flush_list(prev, b, lsns.first); + buf_pool.insert_into_flush_list(prev, b, lsns.first, track_mark); } } @@ -616,8 +621,11 @@ void mtr_t::commit_shrink(fil_space_t &space, uint32_t size) if (slot.type & MTR_MEMO_MODIFY) { modified++; + lsn_t frame_lsn= mach_read_from_8(b->page.frame + FIL_PAGE_LSN); + auto [tracking, track_lsn]= buf_pool.is_tracking(); + bool track_mark= (tracking && frame_lsn <= track_lsn); mach_write_to_8(b->page.frame + FIL_PAGE_LSN, m_commit_lsn); - buf_pool.insert_into_flush_list(prev, b, start_lsn); + buf_pool.insert_into_flush_list(prev, b, start_lsn, track_mark); } } else diff --git a/storage/innobase/os/os0file.cc b/storage/innobase/os/os0file.cc index b495b5454e89b..0878f0fcee4a8 100644 --- a/storage/innobase/os/os0file.cc +++ b/storage/innobase/os/os0file.cc @@ -41,8 +41,13 @@ Created 10/21/1995 Heikki Tuuri # include # include # include +# include #endif +#ifndef _WIN32 +# include +#endif /* !_WIN32 */ + #include "srv0mon.h" #include "srv0srv.h" #include "srv0start.h" @@ -187,6 +192,8 @@ extern uint page_zip_level; /* Keys to register InnoDB I/O with performance schema */ mysql_pfs_key_t innodb_data_file_key; mysql_pfs_key_t innodb_temp_file_key; +mysql_pfs_key_t innodb_arch_file_key; +mysql_pfs_key_t innodb_clone_file_key; #endif /** Handle errors for file operations. @@ -1034,6 +1041,44 @@ bool os_file_create_directory(const char *pathname, bool fail_if_exists) return(true); } +bool +os_file_scan_directory(const char *path, os_dir_cbk_t scan_cbk, bool is_drop) +{ + DIR *directory; + dirent *entry; + + directory = opendir(path); + + if (directory == nullptr) + { + os_file_handle_error_no_exit(path, "opendir", false); + return (false); + } + + entry = readdir(directory); + + while (entry != nullptr) + { + scan_cbk(path, entry->d_name); + entry = readdir(directory); + } + + closedir(directory); + + if (is_drop) + { + int err; + err = rmdir(path); + + if (err != 0) + { + os_file_handle_error_no_exit(path, "rmdir", false); + return false; + } + } + return true; +} + #ifdef O_DIRECT # ifdef __linux__ /** Note that the log file uses buffered I/O. */ @@ -1131,10 +1176,11 @@ os_file_create_func( struct stat st; # endif ut_a(type == OS_LOG_FILE - || type == OS_DATA_FILE || type == OS_DATA_FILE_NO_O_DIRECT); + || type == OS_DATA_FILE || type == OS_DATA_FILE_NO_O_DIRECT + || type == OS_CLONE_DATA_FILE || type == OS_CLONE_LOG_FILE); int direct_flag = 0; - if (type == OS_DATA_FILE) { + if (type == OS_DATA_FILE || type == OS_CLONE_DATA_FILE) { if (!fil_system.is_buffered()) { direct_flag = O_DIRECT; } @@ -1159,7 +1205,8 @@ os_file_create_func( # endif } #else - ut_a(type == OS_LOG_FILE || type == OS_DATA_FILE); + ut_a(type == OS_LOG_FILE || type == OS_DATA_FILE + || type == OS_CLONE_DATA_FILE || type == OS_CLONE_LOG_FILE); constexpr int direct_flag = 0; #endif @@ -1225,6 +1272,8 @@ os_file_create_func( if (!read_only && create_mode != OS_FILE_OPEN_RAW && !my_disable_locking + /* Don't acquire file lock while cloning files. */ + && type != OS_CLONE_DATA_FILE && type != OS_CLONE_LOG_FILE && os_file_lock(file, name)) { if (create_mode == OS_FILE_OPEN_RETRY @@ -1441,6 +1490,30 @@ os_file_size_t os_file_get_size(const char *filename) noexcept return(file_size); } +/** Get available free space on disk +@param[in] path pathname of a directory or file in disk +@param[out] free_space free space available in bytes +@return DB_SUCCESS if all OK */ +static dberr_t os_get_free_space_posix(const char *path, uint64_t &free_space) +{ + struct statvfs stat; + auto ret = statvfs(path, &stat); + + if (ret && (errno == ENOENT || errno == ENOTDIR)) + /* file or directory does not exist */ + return DB_NOT_FOUND; + else if (ret) + { + /* file exists, but stat call failed */ + os_file_handle_error_no_exit(path, "statvfs", false); + return DB_FAIL; + } + free_space= stat.f_bsize; + free_space*= stat.f_bavail; + + return DB_SUCCESS; +} + /** This function returns information about the specified file @param[in] path pathname of the file @param[out] stat_info information of a file in a directory @@ -1968,6 +2041,48 @@ bool os_file_create_directory(const char *pathname, bool fail_if_exists) return(true); } +bool +os_file_scan_directory(const char *path, os_dir_cbk_t scan_cbk, bool is_drop) { + bool file_found; + HANDLE find_hdl; + WIN32_FIND_DATA find_data; + char wild_card_path[MAX_PATH]; + + snprintf(wild_card_path, MAX_PATH, "%s\\*", path); + + find_hdl = FindFirstFile((LPCTSTR)wild_card_path, &find_data); + + if (find_hdl == INVALID_HANDLE_VALUE) + { + os_file_handle_error_no_exit(path, "FindFirstFile", false); + return (false); + } + + do + { + scan_cbk(path, find_data.cFileName); + file_found = FindNextFile(find_hdl, &find_data); + + } while (file_found); + + FindClose(find_hdl); + + if (is_drop) + { + bool ret; + + ret = RemoveDirectory((LPCSTR)path); + + if (!ret) + { + os_file_handle_error_no_exit(path, "RemoveDirectory", false); + return false; + } + } + + return true; +} + /** Get disk sector size for a file. */ static size_t get_sector_size(HANDLE file) { @@ -2037,7 +2152,10 @@ os_file_create_func( break; } - DWORD attributes= FILE_FLAG_OVERLAPPED; + DWORD attributes= 0; + + if (type != OS_CLONE_LOG_FILE && type != OS_CLONE_DATA_FILE) + attributes|= FILE_FLAG_OVERLAPPED; if (type == OS_LOG_FILE) { if (!log_sys.is_opened() && !log_sys.log_buffered) { @@ -2046,7 +2164,8 @@ os_file_create_func( if (log_sys.log_write_through) attributes|= FILE_FLAG_WRITE_THROUGH; } else { - if (type == OS_DATA_FILE && !fil_system.is_buffered()) + if ((type == OS_DATA_FILE || type == OS_CLONE_DATA_FILE) + && !fil_system.is_buffered()) attributes|= FILE_FLAG_NO_BUFFERING; if (fil_system.is_write_through()) attributes|= FILE_FLAG_WRITE_THROUGH; @@ -2054,6 +2173,10 @@ os_file_create_func( DWORD access = read_only ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE; + /* Clone data and log must allow concurrent write to file. */ + if (type == OS_CLONE_LOG_FILE || type == OS_CLONE_DATA_FILE) + share_mode |= FILE_SHARE_WRITE; + for (;;) { const char *operation; @@ -2382,6 +2505,51 @@ os_file_size_t os_file_get_size(const char *filename) noexcept return(file_size); } +/** Get available free space on disk +@param[in] path pathname of a directory or file in disk +@param[out] block_size Block size to use for IO in bytes +@param[out] free_space free space available in bytes +@return DB_SUCCESS if all OK */ +static dberr_t os_get_free_space_win32(const char *path, uint32_t &block_size, + uint64_t &free_space) +{ + char volname[MAX_PATH]; + BOOL result= GetVolumePathName(path, volname, MAX_PATH); + + if (!result) + { + ib::error() + << "os_file_get_status_win32: " + << "Failed to get the volume path name for: " << path + << "- OS error number " << GetLastError(); + return DB_FAIL; + } + + DWORD sectorsPerCluster; + DWORD bytesPerSector; + DWORD numberOfFreeClusters; + DWORD totalNumberOfClusters; + + result= + GetDiskFreeSpace((LPCSTR)volname, §orsPerCluster, &bytesPerSector, + &numberOfFreeClusters, &totalNumberOfClusters); + + if (!result) + { + ib::error() << "GetDiskFreeSpace(" << volname << ",...) " + << "failed " + << "- OS error number " << GetLastError(); + return DB_FAIL; + } + + block_size= bytesPerSector * sectorsPerCluster; + + free_space= static_cast(block_size); + free_space*= numberOfFreeClusters; + + return DB_SUCCESS; +} + /** This function returns information about the specified file @param[in] path pathname of the file @param[out] stat_info information of a file in a directory @@ -2743,6 +2911,121 @@ os_file_read_func( return err ? err : DB_IO_ERROR; } +/** copy data from one file to another file using read, write. +@param[in] src_file file handle to copy from +@param[in] src_offset offset to copy from +@param[in] dest_file file handle to copy to +@param[in] dest_offset offset to copy to +@param[in] size number of bytes to copy +@return DB_SUCCESS if successful */ +static dberr_t os_file_copy_read_write( + os_file_t src_file, + os_offset_t src_offset, + os_file_t dest_file, + os_offset_t dest_offset, + uint size) +{ + static const size_t SECTOR_SIZE = 512; + dberr_t err; + uint request_size; + const uint BUF_SIZE = 4 * SECTOR_SIZE; + + alignas(SECTOR_SIZE) char buf[BUF_SIZE]; + + while (size > 0) { + if (size > BUF_SIZE) { + request_size = BUF_SIZE; + } else { + request_size = size; + } + + err = os_file_read_func(IORequestRead, src_file, &buf, src_offset, + request_size, nullptr); + + if (err != DB_SUCCESS) { + return err; + } + src_offset += request_size; + + err = os_file_write_func(IORequestWrite, "file copy", dest_file, &buf, + dest_offset, request_size); + + if (err != DB_SUCCESS) { + return err; + } + dest_offset += request_size; + size -= request_size; + } + + return DB_SUCCESS; +} + +/** Copy data from one file to another file. Data is read/written +at current file offset. +@param[in] src_file file handle to copy from +@param[in] src_offset offset to copy from +@param[in] dest_file file handle to copy to +@param[in] dest_offset offset to copy to +@param[in] size number of bytes to copy +@return DB_SUCCESS if successful */ +#ifdef __linux__ +dberr_t os_file_copy_func( + os_file_t src_file, + os_offset_t src_offset, + os_file_t dest_file, + os_offset_t dest_offset, + uint size) +{ + dberr_t err; + static bool use_sendfile = true; + + if (!os_file_seek(nullptr, src_file, src_offset)) { + return (DB_IO_ERROR); + } + + if (!os_file_seek(nullptr, dest_file, dest_offset)) { + return (DB_IO_ERROR); + } + + while (use_sendfile && size > 0) { + auto ret_size = sendfile(dest_file, src_file, nullptr, size); + + if (ret_size == -1) { + /* Fall through read/write path. */ + ib::info() << "sendfile failed to copy data" + " : trying read/write "; + + use_sendfile = false; + break; + } + + auto actual_size = static_cast(ret_size); + + ut_ad(size >= actual_size); + size -= actual_size; + } + + if (size == 0) { + return (DB_SUCCESS); + } + + err = os_file_copy_read_write(src_file, src_offset, dest_file, dest_offset, + size); + + return (err); +} +#else /* !__linux__ */ +dberr_t os_file_copy_func(os_file_t src_file, os_offset_t src_offset, + os_file_t dest_file, os_offset_t dest_offset, + uint size) { + dberr_t err; + + err = os_file_copy_read_write(src_file, src_offset, dest_file, dest_offset, + size); + return (err); +} +#endif /* !__linux__ */ + /** Handle errors for file operations. @param[in] name name of a file or NULL @param[in] operation operation @@ -2858,6 +3141,19 @@ static bool os_is_sparse_file_supported(os_file_t fh) noexcept #endif /* _WIN32 */ } +dberr_t os_get_free_space(const char *path, uint64_t &free_space) +{ +#ifdef _WIN32 + uint32_t block_size; + auto err= os_get_free_space_win32(path, block_size, free_space); + +#else /* !_WIN32 */ + auto err= os_get_free_space_posix(path, free_space); + +#endif /* !_WIN32 */ + return err; +} + /** Truncate a file to a specified size in bytes. @param[in] pathname file path @param[in] file file to be truncated @@ -2888,6 +3184,33 @@ os_file_truncate( #endif /* _WIN32 */ } +bool os_file_seek(const char *pathname, os_file_t file, os_offset_t offset) { + bool success = true; + +#ifdef _WIN32 + LARGE_INTEGER length; + + length.QuadPart = offset; + + success = SetFilePointerEx(file, length, nullptr, FILE_BEGIN); + +#else /* !_WIN32 */ + off_t ret; + + ret = lseek(file, offset, SEEK_SET); + + if (ret == -1) { + success = false; + } +#endif /* !_WIN32 */ + + if (!success) { + os_file_handle_error_no_exit(pathname, "os_file_seek", false); + } + + return success; +} + /** Check the existence and type of the given file. @param[in] path path name of file @param[out] exists true if the file exists diff --git a/storage/innobase/row/row0import.cc b/storage/innobase/row/row0import.cc index 214283b602e40..76faa1e3b9335 100644 --- a/storage/innobase/row/row0import.cc +++ b/storage/innobase/row/row0import.cc @@ -24,6 +24,7 @@ Import a tablespace to a running instance. Created 2012-02-08 by Sunny Bains. *******************************************************/ +#include "arch0arch.h" #include "row0import.h" #include "btr0pcur.h" #ifdef BTR_CUR_HASH_ADAPT @@ -4218,6 +4219,11 @@ static dberr_t fil_iterate( src + FIL_PAGE_SPACE_ID); } + /* We are going to modify the page. Add to page + tracking system. */ + arch_sys->page_sys()->track_page(&block->page, LSN_MAX, + LSN_MAX, true); + const uint16_t type = fil_page_get_type(src); page_compressed = (full_crc32 diff --git a/storage/innobase/srv/srv0mon.cc b/storage/innobase/srv/srv0mon.cc index b72ee055b671b..c44bbd13fb148 100644 --- a/storage/innobase/srv/srv0mon.cc +++ b/storage/innobase/srv/srv0mon.cc @@ -1033,6 +1033,31 @@ static monitor_info_t innodb_counter_info[] = MONITOR_NONE, MONITOR_DEFAULT_START, MONITOR_ICP_MATCH}, + /* ========== Page track usage ========== */ + {"module_page_track", "page_track", "Counters related to page tracking", + MONITOR_MODULE, + MONITOR_DEFAULT_START, MONITOR_MODULE_PAGE_TRACK}, + + {"page_track_resets", "page_track", "Number of resets", + MONITOR_NONE, + MONITOR_DEFAULT_START, MONITOR_PAGE_TRACK_RESETS}, + + {"page_track_partial_block_writes", "page_track", + "Number of partial block writes", + MONITOR_NONE, + MONITOR_DEFAULT_START, MONITOR_PAGE_TRACK_PARTIAL_BLOCK_WRITES}, + + {"page_track_full_block_writes", "page_track", + "Number of full block writes", + MONITOR_NONE, + MONITOR_DEFAULT_START, MONITOR_PAGE_TRACK_FULL_BLOCK_WRITES}, + + {"page_track_checkpoint_partial_flush_request", "page_track", + "Number of partial flush requests made during checkpointing", + MONITOR_NONE, + MONITOR_DEFAULT_START, + MONITOR_PAGE_TRACK_CHECKPOINT_PARTIAL_FLUSH_REQUEST}, + /* ========== To turn on/off reset all counters ========== */ {"all", "All Counters", "Turn on/off and reset all counters", MONITOR_MODULE, diff --git a/storage/innobase/srv/srv0srv.cc b/storage/innobase/srv/srv0srv.cc index 998cf8b20236d..4df785ad15c77 100644 --- a/storage/innobase/srv/srv0srv.cc +++ b/storage/innobase/srv/srv0srv.cc @@ -505,6 +505,18 @@ PSI_stage_info srv_stage_buffer_pool_load = {0, "buffer pool load", PSI_FLAG_STAGE_PROGRESS}; #endif /* HAVE_PSI_STAGE_INTERFACE */ +/** Performance schema stage event for monitoring clone file copy progress. */ +PSI_stage_info srv_stage_clone_file_copy + = {0, "clone (file copy)", PSI_FLAG_STAGE_PROGRESS}; + +/** Performance schema stage event for monitoring clone redo copy progress. */ +PSI_stage_info srv_stage_clone_redo_copy + = {0, "clone (redo copy)", PSI_FLAG_STAGE_PROGRESS}; + +/** Performance schema stage event for monitoring clone page copy progress. */ +PSI_stage_info srv_stage_clone_page_copy + = {0, "clone (page copy)", PSI_FLAG_STAGE_PROGRESS}; + /*********************************************************************//** Prints counters for work done by srv_master_thread. */ static diff --git a/storage/innobase/srv/srv0start.cc b/storage/innobase/srv/srv0start.cc index bf76e81f23a3e..845b674bc969a 100644 --- a/storage/innobase/srv/srv0start.cc +++ b/storage/innobase/srv/srv0start.cc @@ -38,9 +38,11 @@ Created 2/16/1996 Heikki Tuuri #include "mysql/psi/mysql_stage.h" #include "mysql/psi/psi.h" +#include "arch0arch.h" #include "row0ftsort.h" #include "ut0mem.h" #include "mem0mem.h" +#include "clone0api.h" #include "data0data.h" #include "data0type.h" #include "dict0dict.h" @@ -127,7 +129,7 @@ static bool srv_started_redo; /** At a shutdown this value climbs from SRV_SHUTDOWN_NONE to SRV_SHUTDOWN_CLEANUP and then to SRV_SHUTDOWN_LAST_PHASE, and so on */ -enum srv_shutdown_t srv_shutdown_state = SRV_SHUTDOWN_NONE; +std::atomic srv_shutdown_state{SRV_SHUTDOWN_NONE}; /** Name of srv_monitor_file */ static char* srv_monitor_file_name; @@ -153,6 +155,9 @@ static PSI_stage_info* srv_stages[] = &srv_stage_alter_table_merge_sort, &srv_stage_alter_table_read_pk_internal_sort, &srv_stage_buffer_pool_load, + &srv_stage_clone_file_copy, + &srv_stage_clone_page_copy, + &srv_stage_clone_redo_copy }; #endif /* HAVE_PSI_STAGE_INTERFACE */ @@ -1075,7 +1080,9 @@ srv_init_abort_low( #endif /* UNIV_DEBUG */ " with error " << err; } - +#ifndef EMBEDDED_LIBRARY + clone_files_error(); +#endif /* EMBEDDED_LIBRARY */ srv_shutdown_bg_undo_sources(); srv_shutdown_threads(true); return(err); @@ -1100,7 +1107,11 @@ ATTRIBUTE_COLD static lsn_t srv_prepare_to_delete_redo_log_file() noexcept const bool latest_format{log_sys.is_latest()}; lsn_t flushed_lsn{log_sys.get_flushed_lsn(std::memory_order_relaxed)}; - if (latest_format && !(log_sys.file_size & 4095) && + /* For clone recovery, we should not need to log file names before deleting + creating new logs. All logs are applied at this point and dirty pages are + flushed. If no new checkpoint is created, the DB should recover fine in case + of a crash before new logs are created. */ + if (!recv_sys.is_cloned_db && latest_format && !(log_sys.file_size & 4095) && flushed_lsn != log_sys.next_checkpoint_lsn + (log_sys.is_encrypted() ? SIZE_OF_FILE_CHECKPOINT + 8 @@ -1326,7 +1337,17 @@ dberr_t srv_start(bool create_new_db) mysql_mutex_init(srv_misc_tmpfile_mutex_key, &srv_misc_tmpfile_mutex, nullptr); } + /* Must replace clone files before opening any files. When clone + replaces current database, cloned files are moved to data files + at this stage. */ +#ifndef EMBEDDED_LIBRARY + if (srv_operation == SRV_OPERATION_NORMAL) + err = clone_init(); + if (err != DB_SUCCESS) { + return (srv_init_abort(err)); + } +#endif /* EMBEDDED_LIBRARY */ if (!srv_read_only_mode) { if (srv_innodb_status) { @@ -1441,6 +1462,9 @@ dberr_t srv_start(bool create_new_db) return srv_init_abort(err); } + if (srv_operation == SRV_OPERATION_NORMAL) + Arch_Sys::init(); + if (create_new_db) { lsn_t flushed_lsn = log_sys.init_lsn(); @@ -1566,6 +1590,10 @@ dberr_t srv_start(bool create_new_db) switch (srv_operation) { case SRV_OPERATION_NORMAL: + if (err == DB_SUCCESS) { + arch_sys->page_sys()->post_recovery_init(); + } + [[fallthrough]]; case SRV_OPERATION_EXPORT_RESTORED: case SRV_OPERATION_RESTORE_EXPORT: if (err != DB_SUCCESS) { @@ -1600,7 +1628,9 @@ dberr_t srv_start(bool create_new_db) if (err != DB_SUCCESS) { return srv_init_abort(err); } - +#ifndef EMBEDDED_LIBRARY + ut_ad(clone_check_recovery_crashpoint(recv_sys.is_cloned_db)); +#endif if (srv_force_recovery < SRV_FORCE_NO_LOG_REDO) { /* Apply the hashed log records to the respective file pages, for the last batch of @@ -1997,7 +2027,12 @@ dberr_t srv_start(bool create_new_db) srv_started_redo = true; } - +#ifndef EMBEDDED_LIBRARY + /* Finish clone files recovery. This call is idempotent and is no op + if it is already done before creating new log files. */ + if (srv_operation == SRV_OPERATION_NORMAL) + clone_files_recovery(true); +#endif /* EMBEDDED_LIBRARY */ return(DB_SUCCESS); } @@ -2066,6 +2101,9 @@ void innodb_shutdown() /* Shut down the persistent files. */ logs_empty_and_mark_files_at_shutdown(); } + /* Copy all log data to archive and stop archiver threads. */ + if (srv_operation == SRV_OPERATION_NORMAL) + Arch_Sys::stop(); os_aio_free(); fil_space_t::close_all(); @@ -2107,7 +2145,6 @@ void innodb_shutdown() /* This must be disabled before closing the buffer pool and closing the data dictionary. */ - #ifdef BTR_CUR_HASH_ADAPT if (dict_sys.is_initialised()) { btr_search.disable(); @@ -2153,6 +2190,14 @@ void innodb_shutdown() << srv_shutdown_lsn << "; transaction id " << trx_sys.get_max_trx_id(); } + + if (srv_operation == SRV_OPERATION_NORMAL) { +#ifndef EMBEDDED_LIBRARY + clone_free(); +#endif /* EMBEDDED_LIBRARY */ + Arch_Sys::free(); + } + srv_thread_pool_end(); srv_started_redo = false; srv_was_started = false; diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index f6ad534ef38cd..439ac00de00f9 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -42,6 +42,7 @@ Created 3/26/1996 Heikki Tuuri #include #include #include "log.h" +#include "clone0api.h" /** Maximum allowable purge history length. <=0 means 'infinite'. */ ulong srv_max_purge_lag = 0; @@ -644,7 +645,11 @@ TRANSACTIONAL_TARGET void trx_purge_truncate_history() if (head.free_history() != DB_SUCCESS) return; - +#ifndef EMBEDDED_LIBRARY + Clone_notify notifier(Clone_notify::Type::SPACE_UNDO_TRUNCATE, + UINT32_MAX, true); + if (notifier.failed()) return; +#endif /* EMBEDDED_LIBRARY */ while (fil_space_t *space= purge_sys.truncating_tablespace()) { for (auto &rseg : trx_sys.rseg_array) diff --git a/storage/innobase/ut/ut0dbg.cc b/storage/innobase/ut/ut0dbg.cc index 167cc71403c1f..f9b55034aa60e 100644 --- a/storage/innobase/ut/ut0dbg.cc +++ b/storage/innobase/ut/ut0dbg.cc @@ -29,6 +29,13 @@ Created 1/30/1994 Heikki Tuuri /*************************************************************//** Report a failed assertion. */ + +static std::function assert_callback; + +void ut_set_assert_callback(std::function &callback) { + assert_callback = callback; +} + ATTRIBUTE_NORETURN void ut_dbg_assertion_failed( @@ -57,5 +64,10 @@ ut_dbg_assertion_failed( fflush(stderr); fflush(stdout); + + /* Call any registered callback function. */ + if (assert_callback) { + assert_callback(); + } abort(); } diff --git a/storage/innobase/ut/ut0new.cc b/storage/innobase/ut/ut0new.cc index a3ce1bdf3c767..3be49c36a30f9 100644 --- a/storage/innobase/ut/ut0new.cc +++ b/storage/innobase/ut/ut0new.cc @@ -38,7 +38,9 @@ Keep this list alphabetically sorted. */ #ifdef BTR_CUR_HASH_ADAPT PSI_memory_key mem_key_ahi; #endif /* BTR_CUR_HASH_ADAPT */ +PSI_memory_key mem_key_archive; PSI_memory_key mem_key_buf_buf_pool; +PSI_memory_key mem_key_clone; PSI_memory_key mem_key_dict_stats_bg_recalc_pool_t; PSI_memory_key mem_key_dict_stats_index_map_t; PSI_memory_key mem_key_dict_stats_n_diff_on_level; @@ -65,7 +67,9 @@ static PSI_memory_info pfs_info[] = { #ifdef BTR_CUR_HASH_ADAPT {&mem_key_ahi, "adaptive hash index", 0}, #endif /* BTR_CUR_HASH_ADAPT */ + {&mem_key_archive, "log and page archiver", 0}, {&mem_key_buf_buf_pool, "buf_buf_pool", 0}, + {&mem_key_clone, "clone", 0}, {&mem_key_dict_stats_bg_recalc_pool_t, "dict_stats_bg_recalc_pool_t", 0}, {&mem_key_dict_stats_index_map_t, "dict_stats_index_map_t", 0}, {&mem_key_dict_stats_n_diff_on_level, "dict_stats_n_diff_on_level", 0}, diff --git a/storage/innobase/ut/ut0ut.cc b/storage/innobase/ut/ut0ut.cc index 679342c90db27..4084b7509cdc4 100644 --- a/storage/innobase/ut/ut0ut.cc +++ b/storage/innobase/ut/ut0ut.cc @@ -40,6 +40,7 @@ Created 5/11/1994 Heikki Tuuri #ifndef DBUG_OFF #include "rem0rec.h" #endif +#include /**********************************************************//** Returns the number of milliseconds since some epoch. The @@ -417,6 +418,8 @@ ut_strerr( return ("File system does not support punch hole (trim) operation."); case DB_PAGE_CORRUPTED: return("Page read from tablespace is corrupted."); + case DB_ABORT_INCOMPLETE_CLONE: + return("Incomplete cloned data directory."); /* do not add default: in order to produce a warning if new code is added to the enum but not added here */ diff --git a/storage/maria/CMakeLists.txt b/storage/maria/CMakeLists.txt index 9bdd729840077..acc68b60df8ee 100644 --- a/storage/maria/CMakeLists.txt +++ b/storage/maria/CMakeLists.txt @@ -44,7 +44,7 @@ SET(ARIA_SOURCES ma_init.c ma_open.c ma_extra.c ma_info.c ma_rkey.c ma_checkpoint.c ma_recovery.c ma_commit.c ma_pagecrc.c ha_maria.h maria_def.h ma_recovery_util.c ma_servicethread.c ma_norec.c - ma_crypt.c ma_backup.c + ma_crypt.c ma_backup.c ma_clone.cc ) IF(APPLE) @@ -57,8 +57,14 @@ IF(CMAKE_SYSTEM_NAME MATCHES AIX) SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-berok") ENDIF() +# Link libstdc++fs for GNU compiler versions < 9 +SET(ARIA_LINK_LIBRARIES myisam mysys mysys_ssl) +IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.0") + LIST(APPEND ARIA_LINK_LIBRARIES stdc++fs) +ENDIF() + MYSQL_ADD_PLUGIN(aria ${ARIA_SOURCES} STORAGE_ENGINE MANDATORY - LINK_LIBRARIES myisam mysys mysys_ssl + LINK_LIBRARIES ${ARIA_LINK_LIBRARIES} RECOMPILE_FOR_EMBEDDED) MYSQL_ADD_EXECUTABLE(aria_ftdump aria_ftdump.c COMPONENT Server) diff --git a/storage/maria/ha_maria.cc b/storage/maria/ha_maria.cc index c5492978e5d36..0e660abf00928 100644 --- a/storage/maria/ha_maria.cc +++ b/storage/maria/ha_maria.cc @@ -73,6 +73,7 @@ const char *zerofill_error_msg= */ ulonglong maria_recover_options= HA_RECOVER_NONE; handlerton *maria_hton; +void init_maria_clone_interfaces(handlerton *aria_hton); /* bits in maria_recover_options */ const char *maria_recover_names[]= @@ -1000,6 +1001,7 @@ can_enable_indexes(0), bulk_insert_single_undo(BULK_INSERT_NONE) handler *ha_maria::clone(const char *name __attribute__((unused)), MEM_ROOT *mem_root) { +#ifndef EMBEDDED_LIBRARY ha_maria *new_handler= static_cast (handler::clone(file->s->open_file_name.str, mem_root)); @@ -1012,6 +1014,9 @@ handler *ha_maria::clone(const char *name __attribute__((unused)), new_handler->file->trn_next == 0); } return new_handler; +#else + return nullptr; +#endif /* !EMBEDDED */ } @@ -3962,7 +3967,7 @@ static int ha_maria_init(void *p) maria_multi_threaded= maria_in_ha_maria= TRUE; maria_create_trn_hook= maria_create_trn_for_mysql; maria_assert_if_crashed_table= debug_assert_if_crashed_table; - + init_maria_clone_interfaces(maria_hton); if (res) { maria_hton= 0; diff --git a/storage/maria/ma_backup.c b/storage/maria/ma_backup.c index 470d3fddc4892..75faf42d7abd4 100644 --- a/storage/maria/ma_backup.c +++ b/storage/maria/ma_backup.c @@ -199,14 +199,17 @@ int aria_read_index(File kfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block, length= _ma_get_page_used(&share, buffer); if (length > cap->block_size - CRC_SIZE) DBUG_RETURN(HA_ERR_CRASHED); - error= maria_page_crc_check(buffer, block, &share, - MARIA_NO_CRC_NORMAL_PAGE, - (int) length); - if (error != HA_ERR_WRONG_CRC) + if (!_ma_check_if_zero(buffer, share.block_size - CRC_SIZE)) + error= 0; + else + error= maria_page_crc_check(buffer, block, &share, + MARIA_NO_CRC_NORMAL_PAGE, + (int) length); + if (error == 0 || my_errno != HA_ERR_WRONG_CRC) DBUG_RETURN(error); } my_sleep(100000); /* Sleep 0.1 seconds */ - } while (retry < MAX_RETRY); + } while (retry++ < MAX_RETRY); DBUG_RETURN(HA_ERR_WRONG_CRC); } @@ -264,15 +267,18 @@ int aria_read_data(File dfile, ARIA_TABLE_CAPABILITIES *cap, ulonglong block, if (length == cap->block_size) { - error= maria_page_crc_check(buffer, block, &share, + if (!_ma_check_if_zero(buffer, share.block_size - CRC_SIZE)) + error= 0; + else + error= maria_page_crc_check(buffer, block, &share, ((block % cap->bitmap_pages_covered) == 0 ? MARIA_NO_CRC_BITMAP_PAGE : MARIA_NO_CRC_NORMAL_PAGE), share.block_size - CRC_SIZE); - if (error != HA_ERR_WRONG_CRC) + if (error == 0 || my_errno != HA_ERR_WRONG_CRC) DBUG_RETURN(error); } my_sleep(100000); /* Sleep 0.1 seconds */ - } while (retry < MAX_RETRY); + } while (retry++ < MAX_RETRY); DBUG_RETURN(HA_ERR_WRONG_CRC); } diff --git a/storage/maria/ma_clone.cc b/storage/maria/ma_clone.cc new file mode 100644 index 0000000000000..31c3839b0900a --- /dev/null +++ b/storage/maria/ma_clone.cc @@ -0,0 +1,1788 @@ +/* + Copyright (c) 2024, 2024, 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-1301, USA +*/ + +/** +@file storage/maria/ma_clone.cc +Clone Aria Tables +Part of the implementation is taken from extra/mariabackup/aria_backup_client.cc +and plugin/clone/src/clone_se.cc +*/ + +#include "handler.h" +#include "clone_handler.h" +#include "mysqld_error.h" +#include "maria_def.h" +#include +#include "log.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef EMBEDDED_LIBRARY +int clone_backup_lock(THD *thd, const char *, const char*) { return 0; } +int clone_backup_unlock(THD *thd) { return 0; } +namespace clone_common +{ + std::string read_table_version_id(File f) { return ""; } + std::tuple + convert_filepath_to_tablename(const char *filepath) + { return std::make_tuple("", "", ""); } + + bool is_stats_table(const char *db, const char *table) + { return false; } + + bool is_log_table(const char *db, const char *table) + { return false; } +} + +#endif + +namespace aria_engine +{ +class Locator +{ + public: + Locator(const Locator *ref_loc, uint32_t clone_index, bool is_copy); + Locator(const unsigned char *serial, size_t serial_length); + + std::pair get_locator() const; + bool operator==(const Locator& other) const; + uint32_t index() const { return m_index; } + + static constexpr uint32_t S_CUR_VERSION= 1; + static constexpr size_t S_MAX_LENGTH= 12; + + private: + void serialize(); + void deserialize(); + + private: + uint32_t m_version= S_CUR_VERSION; + uint32_t m_clone_id= 0; + uint32_t m_index= 0; + unsigned char m_serial[S_MAX_LENGTH]; +}; + +Locator::Locator(const unsigned char *serial, size_t serial_length) +{ + DBUG_ASSERT(serial_length == S_MAX_LENGTH); + memset(&m_serial[0], 0, S_MAX_LENGTH); + auto cp_length= std::min(serial_length, S_MAX_LENGTH); + memcpy(&m_serial[0], serial, cp_length); + deserialize(); +} + +void Locator::serialize() +{ + unsigned char *ptr= &m_serial[0]; + int4store(ptr, m_version); + ptr+= 4; + int4store(ptr, m_clone_id); + ptr+= 4; + int4store(ptr, m_index); +} + +void Locator::deserialize() +{ + unsigned char *ptr= &m_serial[0]; + m_version= uint4korr(ptr); + ptr+= 4; + m_clone_id= uint4korr(ptr); + ptr+= 4; + m_index= uint4korr(ptr); +} + +std::pair Locator::get_locator() const +{ + return std::make_pair(&m_serial[0], static_cast(S_MAX_LENGTH)); +} + +bool Locator::operator==(const Locator& other) const +{ + if (m_clone_id != other.m_clone_id) + return false; + DBUG_ASSERT(m_version == other.m_version); + DBUG_ASSERT(m_index == other.m_index); + return (m_version == other.m_version && m_index == other.m_index); +} + +class Descriptor +{ + public: + Descriptor(const unsigned char *serial, size_t serial_length); + Descriptor(const std::string &file_name, uint64_t offset, bool is_log); + + std::pair get_file_info() const; + std::pair get_descriptor() const; + bool is_log() const { return m_is_log; } + + static constexpr size_t S_MAX_META_LENGTH= 16; + static constexpr size_t S_MAX_LENGTH= S_MAX_META_LENGTH + 2 * FN_REFLEN + 1; + /* Special offset values. */ + static constexpr uint64_t S_OFF_APPEND= std::numeric_limits::max(); + static constexpr uint64_t S_OFF_NO_DATA= S_OFF_APPEND - 1; + + const uint32_t DESC_FLAG_REDO= 0x01; + + private: + uint64_t m_file_offset= 0; + /* Part of 4 byte serialized flags. */ + bool m_is_log= false; + size_t m_file_name_len= 0; + unsigned char m_serial[S_MAX_LENGTH]; +}; + +Descriptor::Descriptor(const unsigned char *serial, size_t serial_length) +{ + DBUG_ASSERT(serial_length <= S_MAX_LENGTH); + memset(&m_serial[0], 0, S_MAX_LENGTH); + auto cp_length= std::min(serial_length, S_MAX_LENGTH); + memcpy(&m_serial[0], serial, cp_length); + + unsigned char *ptr= &m_serial[0]; + m_file_offset= uint8korr(ptr); + ptr+= 8; + uint32_t flags= uint4korr(ptr); + ptr+= 4; + m_file_name_len= uint4korr(ptr); + m_is_log= flags & DESC_FLAG_REDO; +} + +Descriptor::Descriptor(const std::string &file_name, uint64_t offset, + bool is_log) +{ + m_file_offset= offset; + m_is_log= is_log; + m_file_name_len= file_name.length(); + unsigned char *ptr= &m_serial[0]; + memset(ptr, 0, S_MAX_LENGTH); + + int8store(ptr, offset); + ptr+= 8; + + uint32_t flags= 0; + if (m_is_log) flags |= DESC_FLAG_REDO; + int4store(ptr, flags); + ptr+= 4; + + int4store(ptr, static_cast(m_file_name_len)); + ptr+= 4; + + if (m_file_name_len) + { + size_t available_length= S_MAX_LENGTH - S_MAX_META_LENGTH; + uint32_t cp_length= static_cast( + std::min(m_file_name_len, available_length)); + memcpy(ptr, file_name.c_str(), cp_length); + } +} + +std::pair Descriptor::get_file_info() const +{ + auto ptr= reinterpret_cast(&m_serial[0]); + ptr+= S_MAX_META_LENGTH; + return std::make_pair(std::string(ptr, m_file_name_len), m_file_offset); +} + +std::pair Descriptor::get_descriptor() const +{ + auto length= static_cast(m_file_name_len + S_MAX_META_LENGTH); + return std::make_pair(&m_serial[0], length); +} + +static int send_data(Ha_clone_cbk *cbk_ctx, const unsigned char* data, + size_t data_len, uint64_t offset, + const std::string &file_name, bool log_file= false) +{ + Descriptor data_desc(file_name, offset, log_file); + auto [desc, desc_len]= data_desc.get_descriptor(); + cbk_ctx->set_data_desc(desc, desc_len); + cbk_ctx->clear_flags(); + cbk_ctx->set_os_buffer_cache(); + return cbk_ctx->buffer_cbk(const_cast(data), + static_cast(data_len)); +} + +static int send_file(File file_desc, uchar *buf, size_t buf_size, + Ha_clone_cbk *cbk_ctx, const std::string &fname, + const std::string &tname, size_t ©_size, + bool is_log, bool send_file_name= true) +{ + DBUG_ASSERT(file_desc >= 0); + DBUG_ASSERT(buf_size > 0); + if (file_desc < 0 || !cbk_ctx || !buf || buf_size == 0) + { + copy_size= 0; + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "ARIA SE: Clone send file invalid data"); + return ER_INTERNAL_ERROR; + } + + uint64_t offset= send_file_name ? 0 : Descriptor::S_OFF_APPEND; + bool read_all= (copy_size == 0); + int err= 0; + size_t copied_size= 0; + auto chunk_size= read_all ? buf_size : std::min(buf_size, copy_size); + + while (size_t bytes_read= my_read(file_desc, buf, chunk_size, MY_WME)) + { + if (bytes_read == size_t(-1)) + { + my_printf_error(ER_IO_READ_ERROR, "Error: file %s read for table %s", + ME_ERROR_LOG, fname.c_str(), tname.c_str()); + return ER_IO_READ_ERROR; + } + err= send_data(cbk_ctx, buf, bytes_read, offset, + send_file_name ? fname : "", is_log); + if (err) + break; + copied_size+= bytes_read; + + if (!read_all) + { + if (copied_size >= copy_size) + { + DBUG_ASSERT(copy_size == copied_size); + break; + } + auto size_left= copy_size - copied_size; + chunk_size= std::min(chunk_size, size_left); + } + send_file_name= false; + } + if (!err && copied_size == 0) + err= send_data(cbk_ctx, buf, 0, Descriptor::S_OFF_NO_DATA, fname, + is_log); + copy_size= copied_size; + return err; +} + +class Table +{ + public: + struct Partition + { + std::string m_file_path; + File m_files[2]= {-1, -1}; + MY_STAT m_stats[2]; + }; + static constexpr const char *s_extns[]= {".MAI", ".MAD"}; + + Table(std::string &db, std::string &table, std::string &frm_name, + const char *file_path); + ~Table(); + + void add_partition(const Table &partition) + { + DBUG_ASSERT(m_partitioned); + m_partitions.push_back(partition.m_partitions[0]); + } + + int open(THD *thd, bool no_lock); + int copy(Ha_clone_cbk *cbk_ctx); + void close(); + + std::string &get_db() { return m_db; } + std::string &get_table() { return m_table; } + std::string &get_version() { return m_version; } + std::string &get_full_name() { return m_full_name; } + bool is_partitioned() const { return m_partitioned; } + + bool is_online_backup_safe() const + { + DBUG_ASSERT(is_opened()); + return m_cap.online_backup_safe; + } + bool is_stats() const + { + return clone_common::is_stats_table(m_db.c_str(), m_table.c_str()); + } + bool is_log() const + { + return clone_common::is_log_table(m_db.c_str(), m_table.c_str()); + } + bool is_opened() const + { + return !m_partitions.empty() && + m_partitions[0].m_files[0] >= 0 && + m_partitions[0].m_files[1] >= 0; + } + + private: + std::string m_db; + std::string m_table; + std::string m_frm_name; + std::string m_version; + std::string m_full_name; + + bool m_partitioned= false; + std::vector m_partitions; + ARIA_TABLE_CAPABILITIES m_cap; +}; + +Table::Table(std::string &db, std::string &table, std::string &frm_name, + const char *file_path) : + m_db(std::move(db)), m_table(std::move(table)), + m_frm_name(std::move(frm_name)) +{ + m_full_name.assign("`").append(m_db).append("`.`"); + m_full_name.append(m_table).append("`"); + + if (std::strstr(file_path, "#P#")) + m_partitioned= true; + + Partition partition; + const char *ext_pos = std::strrchr(file_path, '.'); + partition.m_file_path.assign(file_path, ext_pos - file_path); + m_partitions.push_back(std::move(partition)); +} + +int Table::open(THD *thd, bool no_lock) +{ + int error= 0; + bool have_capabilities= false; + File frm_file= -1; + bool locked= false; + + if (!no_lock && clone_backup_lock(thd, m_db.c_str(), m_table.c_str())) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error on executing BACKUP LOCK for ARIA table %s", ME_ERROR_LOG, + m_full_name.c_str()); + error= ER_INTERNAL_ERROR; + goto exit; + } + else + locked= !no_lock; +#ifndef DBUG_OFF + if (strcmp(m_table.c_str(), "table_stats") == 0) + DEBUG_SYNC_C("clone_backup_lock"); +#endif + + for (Partition &partition : m_partitions) + { + for (size_t index= 0; index < 2; index++) + { + auto &extn= s_extns[index]; + std::string file_path= partition.m_file_path + extn; + + partition.m_files[index]= mysql_file_open(0, file_path.c_str(), + O_RDONLY | O_SHARE | O_NOFOLLOW | O_CLOEXEC, MYF(MY_WME)); + if (partition.m_files[index] < 0) + { + my_printf_error(ER_CANT_OPEN_FILE, + "Error on file %s open during %s ARIA table copy", ME_ERROR_LOG, + file_path.c_str(), m_full_name.c_str()); + error= ER_CANT_OPEN_FILE; + goto exit; + } + + if (!my_stat(file_path.c_str(), &partition.m_stats[index], MYF(0))) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error: failed to get stat info for file %s of table %s", + ME_ERROR_LOG, file_path.c_str(), m_full_name.c_str()); + error= ER_INTERNAL_ERROR; + goto exit; + } + } + if (!have_capabilities) + { + if ((error= aria_get_capabilities(partition.m_files[0], &m_cap))) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error: ARIA getting capability: %d", ME_ERROR_LOG, error); + goto exit; + } + have_capabilities= true; + } + } + frm_file= mysql_file_open(key_file_frm, (m_frm_name + ".frm").c_str(), + O_RDONLY | O_SHARE, MYF(0)); + if (frm_file < 0) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error on ARIA FRM file open: %s", ME_ERROR_LOG, + (m_frm_name + ".frm").c_str()); + error= ER_INTERNAL_ERROR; + } + +exit: + if (locked && clone_backup_unlock(thd)) + { + my_printf_error(ER_INTERNAL_ERROR, + "Error on BACKUP UNLOCK for ARIA table %s", + ME_ERROR_LOG, m_full_name.c_str()); + error= ER_INTERNAL_ERROR; + } + if (frm_file >= 0) + { + m_version= clone_common::read_table_version_id(frm_file); + mysql_file_close(frm_file, MYF(0)); + } + if (error) close(); + return error; +} + +Table::~Table() +{ + close(); +} + +void Table::close() +{ + for (Partition &partition : m_partitions) + { + for (size_t index= 0; index < 2; index++) + { + auto file_desc= partition.m_files[index]; + if (file_desc >= 0) + mysql_file_close(partition.m_files[index], MYF(0)); + partition.m_files[index]= -1; + } + } +} + +int Table::copy(Ha_clone_cbk *cbk_ctx) +{ + auto buf_size= static_cast(m_cap.block_size); + std::unique_ptr buf(new uchar[buf_size]); + int err= 0; + + for (const auto &part : m_partitions) + { + /* Loop two time for data and index file. */ + for (size_t index= 0; index < 2; index++) + { + size_t data_bytes= 0; + auto &extn= s_extns[index]; + std::string file_path= part.m_file_path + extn; + + for (ulonglong block= 0;; block++) + { + size_t buf_len= buf_size; + if (index) + err= aria_read_data(part.m_files[index], &m_cap, block, buf.get(), + &buf_len); + else + err= aria_read_index(part.m_files[index], &m_cap, block, buf.get()); + if (err == HA_ERR_END_OF_FILE) + { + err= 0; + break; + } + if (err) + { + my_printf_error(ER_IO_READ_ERROR, "Error: file %s read for table %s", + ME_ERROR_LOG, file_path.c_str(), m_full_name.c_str()); + return ER_IO_READ_ERROR; + } + err= send_data(cbk_ctx, buf.get(), buf_len, Descriptor::S_OFF_APPEND, + (block == 0) ? file_path : ""); + if (err) + return err; + data_bytes+= buf_len; + } + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Copied file %s for " + "table %s, %zu bytes", MYF(ME_NOTE | ME_ERROR_LOG_ONLY), + file_path.c_str(), m_full_name.c_str(), data_bytes); + } + } + return 0; +} + +class Log_Files +{ +public: + /** Initialize by checking existing log files on the disk. */ + Log_Files(const char *datadir, uint32_t max_log_no, uint32_t min_log_no= 0); + + uint32_t first() const { return m_first; } + uint32_t count() const { return m_count; } + uint32_t last() const + { + DBUG_ASSERT(m_count > 0); + return m_first + m_count - 1; + } + void report_found() const + { + if (m_count) + sql_print_information("Found %u aria log files, minimum log number %u, " + "maximum log number %u", m_count, m_first, last()); + } + bool check_if_missing(uint32_t logno) const + { + DBUG_ASSERT(logno > 0); + return (!m_count || m_first > logno || last() < logno); + } + + static std::string name_by_index(size_t log_num) + { + static constexpr const char *prefix= "aria_log."; + std::string log_file; + { + std::stringstream ss; + ss << std::setw(8) << std::setfill('0') << log_num; + log_file.append(prefix).append(ss.str()); + } + return log_file; + } + + static std::string name(const char *datadir_path, size_t log_num) + { + std::string log_file(datadir_path); + return log_file.append("/").append(name_by_index(log_num)); + } + + private: + /** Check to see if a file exists. Takes name of the file to check. + @return true if file exists. */ + static bool file_exists(const char *filename) + { + MY_STAT stat_arg; + auto stat= my_stat(filename, &stat_arg, MYF(0)); + return (stat != nullptr); + } + /* + Skip all missing log files and find the greatest existing log file, or + Skip all existing log files and find the greatest missing log file. + + @param datadir - Search files in this directory + @param start - Start searching from this log number + @param stop - Search up to this point excluding stop + @param kind - true - search for an existing file + false - search for a missing file. + @returns - (stop..start] - the greatest found log file + of the searched kind + - 0 - if no log files of this kind + were found in the range (stop..start]. + */ + static uint32_t find_greatest(const char *datadir, uint32_t start, + uint32_t stop, bool kind) + { + for (uint32_t i= start; i > stop; i--) + { + if (file_exists(name(datadir, i).c_str()) == kind) + return i; + } + return stop; // No log files of the searched kind were found + } + + static uint32_t find_greatest_existing(const char *datadir, uint32_t start, + uint32_t stop) + { + return find_greatest(datadir, start, stop, true); + } + + static uint32_t find_greatest_missing(const char *datadir, uint32_t start, + uint32_t stop) + { + return find_greatest(datadir, start, stop, false); + } + + private: + uint32_t m_first= 0; + uint32_t m_count= 0; +}; + +Log_Files::Log_Files(const char *datadir, uint32_t max_log_no, + uint32_t min_log_no) +{ + auto end= find_greatest_existing(datadir, max_log_no, min_log_no); + DBUG_ASSERT(end >= min_log_no); + if (end == min_log_no + 1) + { + // Just the very one log file (aria_log.00000001 when min_log_no= 0) was found. + m_first= min_log_no + 1; + m_count= 1; + } + else if (end > min_log_no + 1) + { + // Multiple files were found + m_first= find_greatest_missing(datadir, end - 1, min_log_no) + 1; + m_count= 1 + end - m_first; + return; + } + else + { + DBUG_ASSERT(end == min_log_no); + // No log files were found at all + m_first= 0; + m_count= 0; + } +} + +class Job_Repository +{ + public: + using Job= std::function; + void add_one(Job &&job); + void finish(int err, Ha_clone_stage stage); + int consume(THD *thd, uint32_t thread_id, Ha_clone_cbk *cbk, + Ha_clone_stage stage, int err); + int wait_pending(THD *thd); + Ha_clone_stage last_finished_stage(); + + private: + std::mutex m_mutex; + std::condition_variable m_cv; + std::queue m_jobs; + bool m_finished[HA_CLONE_STAGE_MAX]= {false}; + int m_error= 0; + uint32_t n_pending= 0; +}; + +int Job_Repository::wait_pending(THD *thd) +{ + auto cond_fn= [&] + { + return (n_pending == 0); + }; + std::unique_lock lock(m_mutex); + /* We try consuming first and then come here. No more jobs can be added at + this point. */ + DBUG_ASSERT(m_jobs.empty()); + uint32_t count= 0; + constexpr uint32_t max_count= 300; + while (n_pending && ++count < max_count) + { + m_cv.wait_for(lock, std::chrono::seconds(1), cond_fn); + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(ME_ERROR_LOG)); + m_error= ER_QUERY_INTERRUPTED; + return m_error; + } + } + if (n_pending) + { + my_printf_error(ER_STATEMENT_TIMEOUT, + "ARIA SE: Clone Timeout(5 minutes) while waiting for jobs to finish", + MYF(ME_ERROR_LOG)); + m_error= ER_STATEMENT_TIMEOUT; + } + return m_error; +} + +void Job_Repository::add_one(Job &&job) +{ + std::unique_lock lock(m_mutex); + m_jobs.push(std::forward(job)); + ++n_pending; + DBUG_ASSERT(n_pending >= m_jobs.size()); + lock.unlock(); + m_cv.notify_one(); +} + +void Job_Repository::finish(int err, Ha_clone_stage stage) +{ + std::unique_lock lock(m_mutex); + if (stage < HA_CLONE_STAGE_MAX) + m_finished[stage]= true; + if (err && !m_error) + m_error= err; + lock.unlock(); + m_cv.notify_all(); +} + +int Job_Repository::consume(THD *thd, uint32_t thread_id, Ha_clone_cbk *cbk, + Ha_clone_stage stage, int err) +{ + std::unique_lock lock(m_mutex); + while (!m_finished[stage] || !m_jobs.empty()) + { + while (!m_jobs.empty()) + { + auto job= std::move(m_jobs.front()); + m_jobs.pop(); + lock.unlock(); + /* Even after an error, we need to keep consuming all jobs added as jobs + could hold table object ownership that needs to be freed. The input + error would ensure we don't actually transfer any data after an error. */ + err= job(thd, cbk, thread_id, err); + DBUG_ASSERT(n_pending > 0); + --n_pending; + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(0)); + err= ER_QUERY_INTERRUPTED; + } + lock.lock(); + } + if (m_error && !err) + { + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "ARIA SE: Clone error in concurrent task"); + err= m_error; + break; + } + else if (err && !m_error) + { + m_error= err; + break; + } + m_cv.wait_for(lock, std::chrono::seconds(1), [&] + { + return (m_finished[stage] || !m_jobs.empty() || m_error); + }); + } + return err; +} + +Ha_clone_stage Job_Repository::last_finished_stage() +{ + Ha_clone_stage last_stage= HA_CLONE_STAGE_MAX; + std::unique_lock lock(m_mutex); + auto stage= HA_CLONE_STAGE_CONCURRENT; + while (stage < HA_CLONE_STAGE_MAX) + { + if (m_finished[stage] == false) + { + last_stage= stage; + break; + } + stage= static_cast(stage + 1); + } + lock.unlock(); + return last_stage; +} + +using table_key_t= std::string; + +inline table_key_t table_key(const std::string &db, const std::string &table) +{ + return std::string(db).append(".").append(table); +} + +struct Thread_Context +{ + int open(const std::string &path, const std::string &file, uint64_t offset, + bool log= false); + int open_for_read(const std::string &path, const std::string &file, + bool log= false); + void close(); + void close_log(); + + uint32_t m_task_id= 0; + File m_file= -1; + File m_log_file= -1; + std::string m_cur_data_file; +}; + +int Thread_Context::open_for_read(const std::string &path, + const std::string &file, bool log) +{ + log ? close_log() : close(); + auto &cur_file= log ? m_log_file : m_file; + + char fullpath[FN_REFLEN]; + fn_format(fullpath, file.c_str(), path.c_str(), "", MYF(MY_RELATIVE_PATH)); + + int open_flags= O_RDONLY | O_SHARE; + cur_file= mysql_file_open(0, fullpath, open_flags, MYF(0)); + + if (cur_file < 0) + { + cur_file= -1; + my_error(ER_CANT_OPEN_FILE, MYF(ME_ERROR_LOG), fullpath, my_errno); + return ER_CANT_OPEN_FILE; + } + return 0; +} + +int Thread_Context::open(const std::string &path, const std::string &file, + uint64_t offset, bool log) +{ + /* Close previous file if there. */ + log ? close_log() : close(); + auto &cur_file= log ? m_log_file : m_file; + + char fullpath[FN_REFLEN]; + fn_format(fullpath, file.c_str(), path.c_str(), "", MYF(MY_RELATIVE_PATH)); + + size_t dirpath_len= 0; + char dirpath[FN_REFLEN]; + dirname_part(dirpath, fullpath, &dirpath_len); + + /* Make schema directory path and create file, if needed. */ + if (my_mkdir(dirpath, 0777, MYF(0)) >= 0 || my_errno == EEXIST) + { + int open_flags= O_WRONLY | O_BINARY; + + if (offset == Descriptor::S_OFF_APPEND) + open_flags|= O_APPEND; + else + DBUG_ASSERT(offset == Descriptor::S_OFF_NO_DATA || !offset); + + cur_file= mysql_file_open(0, fullpath, open_flags, MYF(0)); + if (cur_file < 0) + { + open_flags|= O_CREAT; + cur_file= mysql_file_open(0, fullpath, open_flags, MYF(0)); + } + } + if (cur_file < 0) + { + cur_file= -1; + my_error(ER_CANT_OPEN_FILE, MYF(ME_ERROR_LOG), fullpath, my_errno); + return ER_CANT_OPEN_FILE; + } + if (!log) + m_cur_data_file.assign(file); + return 0; +} + +void Thread_Context::close_log() +{ + if (m_log_file < 0) + return; + mysql_file_close(m_log_file, MYF(0)); + m_log_file= -1; +} + +void Thread_Context::close() +{ + if (m_file < 0) + return; + mysql_file_close(m_file, MYF(0)); + m_file= -1; +} + +class Clone_Handle +{ + public: + Clone_Handle(bool is_copy, const Locator *ref_loc, const char *datadir, + uint32_t index) : m_is_copy(is_copy), m_loc(ref_loc, index, is_copy), + m_data_dir(datadir ? datadir : "."), m_log_dir(maria_data_root) {} + + void set_error(int err); + int check_error(THD *thd); + + int clone_low(THD *thd, uint32_t task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk); + int clone(THD *thd, uint32_t task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk); + int apply(THD *thd, uint32_t task_id, Ha_clone_cbk *cbk); + + size_t attach(); + bool detach(size_t id); + + Locator &get_locator() { return m_loc; } + static constexpr size_t S_MAX_TASKS= 128; + + bool max_task_reached() const + { + DBUG_ASSERT(m_next_task <= S_MAX_TASKS); + return m_next_task >= S_MAX_TASKS; + } + + private: + int scan(bool no_lock); + int copy_offline_tables(const std::unordered_set &exclude_tables, + bool no_lock, bool copy_stats); + int copy_log_tail(THD *thd, Ha_clone_cbk *cbk_ctx, bool finalize); + + int copy_table_job(Table *table_ptr, bool online_only, bool copy_stats, + bool no_lock, THD *thd, Ha_clone_cbk *cbk, uint32_t thread_id, + int in_error); + + int copy_file_job(std::string *file_name_ptr, bool is_log, THD *thd, + Ha_clone_cbk *cbk, uint32_t thread_id, int in_error); + + int copy_partial_tail(Ha_clone_cbk *cbk_ctx); + + int copy_finish_tail(Ha_clone_cbk *cbk_ctx); + + private: + bool m_is_copy= true; + /** Number of threads attached; Protected by Clone_Sys::mutex_ */ + size_t m_num_threads= 0; + size_t m_next_task= 0; + int m_error= 0; + + Locator m_loc; + std::string m_data_dir; + std::string m_log_dir; + + std::array m_thread_ctxs; + Job_Repository m_jobs; + + std::mutex m_offline_tables_mutex; + std::vector> m_offline_tables; + + size_t m_last_log_num= 0; + size_t m_last_log_offset= 0; +}; + +size_t Clone_Handle::attach() +{ + /* ID is the index into the m_thread_ctxs vector. */ + auto id= m_next_task++; + DBUG_ASSERT(id < S_MAX_TASKS); + + auto &ctx= m_thread_ctxs[id]; + ctx.m_task_id= static_cast(id); + DBUG_ASSERT(ctx.m_file == -1); + + m_num_threads++; + DBUG_ASSERT(m_thread_ctxs.size() >= m_num_threads); + + return id; +} + +bool Clone_Handle::detach(size_t id) +{ + auto &ctx= m_thread_ctxs[id]; + ctx.close(); + ctx.close_log(); + DBUG_ASSERT(m_num_threads > 0); + return (0 == --m_num_threads); +} + +int Clone_Handle::copy_file_job(std::string *file_name_ptr, bool is_log, + THD *thd, Ha_clone_cbk *cbk, uint32_t, int in_error) +{ + std::unique_ptr file_name(file_name_ptr); + int err= in_error; + if (err) + return err; + + std::string file_path; + if (is_log) + { + file_path.assign(m_log_dir); + if (file_path.back() != FN_LIBCHAR) + file_path+= FN_LIBCHAR; + } + file_path.append(*file_name); + + File file= mysql_file_open(0, file_path.c_str(), O_RDONLY | O_SHARE, + MYF(0)); + if (file < 0) + { + my_printf_error(ER_CANT_OPEN_FILE, "Error on opening file: %s", + MYF(ME_ERROR_LOG), file_name->c_str()); + err= ER_CANT_OPEN_FILE; + } + else + { + size_t copy_size= 0; + static const size_t buf_size = 10 * 1024 * 1024; + std::unique_ptr buf= std::make_unique(buf_size); + + err= send_file(file, buf.get(), buf_size, cbk, (*file_name), "", + copy_size, is_log); + mysql_file_close(file, MYF(0)); + + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Copied complete redo log " + "file %s of size %zu bytes", MYF(ME_NOTE | ME_ERROR_LOG_ONLY), + file_name->c_str(), copy_size); + } + return err; +} + +int Clone_Handle::copy_table_job(Table *table_ptr, bool online_only, + bool copy_stats, bool no_lock, + THD *thd, Ha_clone_cbk *cbk, uint32_t, + int in_error) +{ + std::unique_ptr
table(table_ptr); + if (in_error) + return in_error; + + int err= table->open(thd, no_lock); + if (err) + return err; + + bool is_online= table->is_online_backup_safe(); + bool is_stats= table->is_stats(); + bool need_copy= (!online_only || is_online) && (copy_stats || !is_stats); + + if (need_copy) + err= table->copy(cbk); + + table->close(); + + if (!need_copy) + { + std::lock_guard lock(m_offline_tables_mutex); + m_offline_tables.push_back(std::move(table)); + return 0; + } + +#ifndef DBUG_OFF +if (strcmp(table->get_table().c_str(), "t_dml") == 0) + DEBUG_SYNC_C("after_aria_table_copy_t_dml"); +#endif /* DBUG_OFF */ + + /* TODO: Post Copy Hook for DDL */ + // if (!err && m_table_post_copy_hook) + // m_table_post_copy_hook(table->get_db(), table->get_table(), + // table->get_version()); + return err; +} + +int Clone_Handle::scan(bool no_lock) +{ + auto ctrl_file_name= std::make_unique("aria_log_control"); + + using namespace std::placeholders; + m_jobs.add_one(std::bind(&Clone_Handle::copy_file_job, this, + ctrl_file_name.release(), true, _1, _2, _3, _4)); + + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Start scanning engine table" + "s, need backup locks: %d", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), no_lock); +#ifndef EMBEDDED_LIBRARY + std::set ext_list= {".MAD"}; + std::unordered_map> partitioned_tables; + + clone_common::foreach_file_in_dir(m_data_dir, + [&](const fsys::path& file_path) + { + const char* fpath= nullptr; +#ifdef _WIN32 + std::wstring wstr= file_path.wstring(); + int size= WideCharToMultiByte(CP_UTF8, 0, &wstr[0], + (int)wstr.size(), nullptr, + 0, nullptr, nullptr); + std::string fil_path(size, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstr[0], + (int)wstr.size(), &fil_path[0], + size, nullptr, nullptr); + fpath= fil_path.c_str(); +#else /* _WIN32 */ + fpath= file_path.c_str(); +#endif /* _WIN32 */ + + /* TODO: Partial Backup */ + // if (check_if_skip_table(file_path)) + // { + // my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Skipping %s.", + // MYF(ME_NOTE | ME_ERROR_LOG_ONLY), file_path); + // return; + // } + auto db_table_fs= + clone_common::convert_filepath_to_tablename(fpath); + auto tk= table_key(std::get<0>(db_table_fs), std::get<1>(db_table_fs)); + + auto table= std::make_unique
(std::get<0>(db_table_fs), + std::get<1>(db_table_fs), std::get<2>(db_table_fs), fpath); + + if (table->is_log()) + return; + + if (table->is_partitioned()) + { + auto table_it= partitioned_tables.find(table->get_full_name()); + if (table_it == partitioned_tables.end()) + partitioned_tables[table->get_full_name()]= std::move(table); + else + table_it->second->add_partition(*table); + return; + } + using namespace std::placeholders; + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table.release(), true, false, no_lock,_1, _2, _3, _4)); + }, ext_list); + + for (auto &table_it : partitioned_tables) + { + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table_it.second.release(), true, false, no_lock, _1, _2, _3, _4)); + } + + auto horizon= translog_get_horizon(); + uint32_t last_file_num= LSN_FILE_NO(horizon); + + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Start scanning engine redo" + "logs, last log number: %u", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), last_file_num); + + Log_Files logs(m_log_dir.c_str(), last_file_num); + + DEBUG_SYNC_C("after_scanning_log_files"); + + for (auto i= logs.first(); i < logs.last(); ++i) + { + auto log_file= std::make_unique(Log_Files::name_by_index(i)); + m_jobs.add_one(std::bind(&Clone_Handle::copy_file_job, this, + log_file.release(), true, _1, _2, _3, _4)); + } + m_last_log_num= logs.last(); + m_last_log_offset= 0; + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Stop scanning engine " + "tables", MYF(ME_NOTE | ME_ERROR_LOG_ONLY)); +#endif /* EMBEDDED_LIBRARY */ + return 0; +} + +int Clone_Handle::copy_offline_tables( + const std::unordered_set &exclude_tables, + bool no_lock, bool copy_stats) +{ + std::vector> ignored_tables; + for(;;) + { + std::unique_lock lock(m_offline_tables_mutex); + if (m_offline_tables.empty()) + break; + auto table= std::move(m_offline_tables.back()); + m_offline_tables.pop_back(); + lock.unlock(); + auto tkey= table_key(table->get_db(), table->get_table()); + if ((!exclude_tables.empty() && exclude_tables.count(tkey)) || + (!copy_stats && table->is_stats())) + { + ignored_tables.push_back(std::move(table)); + continue; + } + using namespace std::placeholders; + m_jobs.add_one(std::bind(&Clone_Handle::copy_table_job, this, + table.release(), false, copy_stats, no_lock, _1, _2, _3, _4)); + } + if (!ignored_tables.empty()) + { + std::lock_guard lock(m_offline_tables_mutex); + m_offline_tables= std::move(ignored_tables); + } + return 0; +} + +int Clone_Handle::copy_finish_tail(Ha_clone_cbk *cbk_ctx) +{ + int err= 0; + DBUG_ASSERT(m_last_log_num > 0); + if (m_last_log_num == 0) + return 0; + + auto &ctx= m_thread_ctxs[0]; + auto log_file= + std::make_unique(Log_Files::name_by_index(m_last_log_num)); + + /* If the tail log file is not opened yet, send the entire log file. */ + if (ctx.m_log_file == -1) + return copy_file_job(log_file.release(), true, nullptr, cbk_ctx, 0, 0); + + /* Send the rest of the log file. */ + size_t copy_size= 0; + static const size_t buf_size= 1024 * 1024; + std::unique_ptr buf= std::make_unique(buf_size); + err= send_file(ctx.m_log_file, buf.get(), buf_size, cbk_ctx, (*log_file), "", + copy_size, true, false); + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Copied rest of the redo log" + " file %s of size %zu bytes from offset %zu bytes", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), log_file->c_str(), copy_size, + m_last_log_offset); + + ctx.close_log(); + m_last_log_num= 0; + m_last_log_offset= 0; + + if (err) + return err; + + /* Send the header again to update LSN. */ + if ((err= ctx.open_for_read(m_log_dir, (*log_file), true))) + return err; + copy_size= LOG_HEADER_DATA_SIZE; + err= send_file(ctx.m_log_file, buf.get(), buf_size, cbk_ctx, (*log_file), "", + copy_size, true); + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Updated header of redo log" + " file %s of size %zu bytes", MYF(ME_NOTE | ME_ERROR_LOG_ONLY), + log_file->c_str(), copy_size); + ctx.close_log(); + m_last_log_num= 0; + m_last_log_offset= 0; + return err; +} + +template +static T align_down(T value, T alignment) +{ + DBUG_ASSERT(alignment != 0); + DBUG_ASSERT((alignment & (alignment - 1)) == 0); + return value & ~(alignment - 1); +} + +int Clone_Handle::copy_partial_tail(Ha_clone_cbk *cbk_ctx) +{ + int err= 0; + DBUG_ASSERT(m_last_log_num > 0); + if (m_last_log_num == 0) + return err; + auto log_file= + std::make_unique(Log_Files::name_by_index(m_last_log_num)); + auto &ctx= m_thread_ctxs[0]; + + bool send_file_name= false; + if (ctx.m_log_file < 0) + { + send_file_name= true; + if ((err= ctx.open_for_read(m_log_dir, (*log_file), true))) + return err; + } + MY_STAT stat_info; + memset(&stat_info, 0, sizeof(MY_STAT)); + if (my_fstat(ctx.m_log_file, &stat_info, MYF(0))) + { + my_printf_error(ER_INTERNAL_ERROR, "Error: failed to get stat info for " + "ARIA log file %s", ME_ERROR_LOG, log_file->c_str()); + return ER_INTERNAL_ERROR; + } + size_t file_size= static_cast(stat_info.st_size); + + if (file_size <= m_last_log_offset) + { + DBUG_ASSERT(file_size == m_last_log_offset); + return 0; + } + /* Copy without the last page, which can be rewritten. */ + auto copy_size= static_cast(file_size - m_last_log_offset); + copy_size= align_down(copy_size, static_cast(TRANSLOG_PAGE_SIZE)); + if (copy_size <= TRANSLOG_PAGE_SIZE) + return 0; + copy_size-= TRANSLOG_PAGE_SIZE; + DBUG_ASSERT(copy_size > 0); + + static const size_t buf_size= 1024 * 1024; + std::unique_ptr buf= std::make_unique(buf_size); + err= send_file(ctx.m_log_file, buf.get(), buf_size, cbk_ctx, (*log_file), "", + copy_size, true, send_file_name); + if (!err) + my_printf_error(ER_CLONE_SERVER_TRACE, "ARIA SE: Copied partial redo log " + "file %s of size %zu bytes from offset %zu bytes", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), log_file->c_str(), copy_size, + m_last_log_offset); + + m_last_log_offset+= copy_size; + return err; +} + +int Clone_Handle::copy_log_tail(THD *thd, Ha_clone_cbk *cbk_ctx, bool finalize) +{ + int err= 0; + if (finalize && (err= m_jobs.wait_pending(thd))) + return err; + + /* Check for new log files added. */ + auto horizon= translog_get_horizon(); + uint32_t last_file_num= LSN_FILE_NO(horizon); + Log_Files logs(m_log_dir.c_str(), last_file_num, + static_cast(m_last_log_num)); + + if (!logs.count()) + { + /* No new log files. */ + err= finalize ? copy_finish_tail(cbk_ctx) : copy_partial_tail(cbk_ctx); + return err; + } + /* There are more log files added. Finish the current one and continue + with the rest. */ + if ((err= copy_finish_tail(cbk_ctx))) + return err; + + for (auto i= logs.first(); i < logs.last(); ++i) + { + auto log_file= std::make_unique(Log_Files::name_by_index(i)); + if ((err= copy_file_job(log_file.release(), true, nullptr, cbk_ctx, 0, 0))) + return err; + } + /* Set new tail log. */ + m_last_log_num= logs.last(); + m_last_log_offset= 0; + err= finalize ? copy_finish_tail(cbk_ctx) : copy_partial_tail(cbk_ctx); + return err; +} + +class Clone_Sys +{ + public: + int start(bool is_copy, bool attach, Clone_Handle *&clone_hdl, uint32_t &id, + const Locator *ref_loc= nullptr, const char *data_dir= nullptr); + int stop(bool is_copy, Clone_Handle *&clone_hdl, uint32_t task_id); + + Clone_Handle *find(const Locator *in_loc, bool is_copy); + Clone_Handle *get(uint32_t index, bool is_copy); + + uint32_t next_id() { return m_next_clone_id++; } + + static constexpr uint32_t S_MAX_CLONE= 1; + static std::mutex mutex_; + private: + std::mutex m_mutex; + uint32_t m_next_clone_id= 1; + + std::array m_copy_clones; + std::array m_apply_clones; +}; +inline std::mutex Clone_Sys::mutex_; + +int Clone_Sys::start(bool is_copy, bool attach, Clone_Handle *&clone_hdl, + uint32_t &id, const Locator *ref_loc, + const char *data_dir) +{ + if (!attach) + { + /* Create a new clone handle. */ + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + + uint32_t index= 0; + for (auto clone_ : clones) + { + if (clone_ == nullptr) + break; + ++index; + } + if (index >= S_MAX_CLONE) + { + /* Too many active clones .*/ + my_error(ER_CLONE_TOO_MANY_CONCURRENT_CLONES, MYF(ME_ERROR_LOG), + S_MAX_CLONE); + return ER_CLONE_TOO_MANY_CONCURRENT_CLONES; + } + clones[index]= new(std::nothrow) Clone_Handle(is_copy, ref_loc, data_dir, + index); + clone_hdl= clones[index]; + } + if (!clone_hdl) + { + DBUG_ASSERT(attach); + /* Operation has finished already */ + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "ARIA SE: Clone add task refers non-existing clone"); + /* No active clone to attach to. */ + return ER_INTERNAL_ERROR; + } + + if (clone_hdl->max_task_reached()) + { + DBUG_ASSERT(attach); + my_error(ER_INTERNAL_ERROR, MYF(ME_ERROR_LOG), + "ARIA SE: Maximum Tasks reached"); + return ER_INTERNAL_ERROR; + } + id= static_cast(clone_hdl->attach()); + return 0; +} + +int Clone_Sys::stop(bool is_copy, Clone_Handle *&clone_hdl, uint32_t task_id) +{ + bool last= clone_hdl->detach(static_cast(task_id)); + if (last) + { + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + auto index= clone_hdl->get_locator().index(); + DBUG_ASSERT(clones[index] == clone_hdl); + clones[index]= nullptr; + delete clone_hdl; + clone_hdl= nullptr; + } + return 0; +} + +Clone_Handle *Clone_Sys::find(const Locator *in_loc, bool is_copy) +{ + if (!in_loc) + return nullptr; + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + + for (auto clone_hdl : clones) + { + if (!clone_hdl) + continue; + + auto& loc= clone_hdl->get_locator(); + if (loc == *in_loc) + return clone_hdl; + } + return nullptr; +} + +Clone_Handle *Clone_Sys::get(uint32_t index, bool is_copy) +{ + if (index > S_MAX_CLONE) + return nullptr; + auto &clones= is_copy ? m_copy_clones : m_apply_clones; + return clones[index]; +} + +static Clone_Sys clone_system; +static Clone_Sys *const clone_sys= &clone_system; + +Locator::Locator(const Locator *ref_loc, uint32_t clone_index, bool is_copy) +{ + m_version= S_CUR_VERSION; + if (ref_loc && m_version > ref_loc->m_version) + m_version= ref_loc->m_version; + m_index= clone_index; + + uint32_t ref_id= ref_loc ? ref_loc->m_clone_id : 0; + m_clone_id= is_copy ? clone_sys->next_id() : ref_id; + serialize(); +} + +int Clone_Handle::check_error(THD *thd) +{ + if (thd_killed(thd)) + { + my_error(ER_QUERY_INTERRUPTED, MYF(ME_ERROR_LOG)); + set_error(ER_QUERY_INTERRUPTED); + } + const std::lock_guard lock(Clone_Sys::mutex_); + return m_error; +} + +void Clone_Handle::set_error(int err) +{ + if (err == 0) + return; + std::unique_lock lock(Clone_Sys::mutex_); + if (m_error) + return; + m_error= err; + lock.unlock(); + + if (m_is_copy) + m_jobs.finish(err, HA_CLONE_STAGE_MAX); +} + +int Clone_Handle::apply(THD *thd, uint32_t task_id, Ha_clone_cbk *cbk) +{ + uint32_t desc_len= 0; + auto desc_buf= cbk->get_data_desc(&desc_len); + + Descriptor clone_desc(desc_buf, desc_len); + auto &ctx= m_thread_ctxs[task_id]; + + auto [file_name, offset]= clone_desc.get_file_info(); + /* Currently the write is append only or over-write */ + DBUG_ASSERT(!offset || offset == Descriptor::S_OFF_APPEND || + offset == Descriptor::S_OFF_NO_DATA); + + bool is_log= clone_desc.is_log(); + int err= 0; + if (!file_name.empty() && + (err= ctx.open(m_data_dir, file_name, offset, is_log))) + return err; + + if (offset == Descriptor::S_OFF_NO_DATA) + { + is_log ? ctx.close_log() : ctx.close(); + return 0; + } + auto &cur_file= is_log ? ctx.m_log_file : ctx.m_file; + Ha_clone_file file; + DBUG_ASSERT(cur_file >= 0); + if (cur_file < 0) + { + my_error(err, MYF(ME_ERROR_LOG), + "ARIA SE: Cannot apply data- missing file name"); + return ER_INTERNAL_ERROR; + } +#ifdef _WIN32 + file.type= Ha_clone_file::FILE_HANDLE; + file.file_handle= static_cast(my_get_osfhandle(cur_file)); +#else + file.type= Ha_clone_file::FILE_DESC; + file.file_desc= cur_file; +#endif /* _WIN32 */ + + cbk->set_os_buffer_cache(); + return cbk->apply_file_cbk(file); +} + +int Clone_Handle::clone_low(THD *thd, uint32_t task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk) +{ + int err= 0; + bool copy_tail= false; + std::unordered_set tables_in_use; + switch (stage) + { + case HA_CLONE_STAGE_CONCURRENT: + if (task_id != 0) + break; + err= scan(false); + copy_tail= true; + break; + case HA_CLONE_STAGE_NT_DML_BLOCKED: + if (task_id != 0) + break; + /* TODO: get_tables_in_use() : "SHOW OPEN TABLES WHERE In_use = 1" */ + err= copy_offline_tables(tables_in_use, false, false); + copy_tail= true; + break; + case HA_CLONE_STAGE_DDL_BLOCKED: + if (task_id != 0) + break; + tables_in_use.clear(); + err= copy_offline_tables(tables_in_use, true, false); + copy_tail= true; + break; + case HA_CLONE_STAGE_SNAPSHOT: + if (task_id != 0) + break; + tables_in_use.clear(); + err= copy_offline_tables(tables_in_use, true, true); + copy_tail= true; + break; + case HA_CLONE_STAGE_END: + break; + case HA_CLONE_STAGE_MAX: + DBUG_ASSERT(false); + err= ER_INTERNAL_ERROR; + my_error(err, MYF(ME_ERROR_LOG), "ARIA SE: Invalid Execution Stage"); + break; + } + if (task_id == 0) + m_jobs.finish(err, stage); + err= m_jobs.consume(thd, task_id, cbk, stage, err); + set_error(err); + + if (!err && copy_tail) + { + DBUG_ASSERT(task_id == 0); + err= copy_log_tail(thd, cbk, stage == HA_CLONE_STAGE_SNAPSHOT); + } + return err; +} + +int Clone_Handle::clone(THD *thd, uint32_t task_id, Ha_clone_stage stage, + Ha_clone_cbk *cbk) +{ + int err= 0; + Ha_clone_stage cur_stage= m_jobs.last_finished_stage(); + while (!err && cur_stage <= stage) + { + err= clone_low(thd, task_id, cur_stage, cbk); + cur_stage= static_cast(cur_stage + 1); + } + return err; +} +} // namespace aria_engine + +#ifndef EMBEDDED_LIBRARY +static void clone_get_capability(Ha_clone_flagset &flags) +{ + flags.reset(); + flags.set(HA_CLONE_BLOCKING); + flags.set(HA_CLONE_MULTI_TASK); +} + +static int clone_begin(THD *, const uchar *&loc, uint &loc_len, + uint &task_id, Ha_clone_type, Ha_clone_mode mode) +{ + aria_engine::Locator *in_loc= nullptr; + if (loc) + in_loc= new(std::nothrow) aria_engine::Locator(loc, loc_len); + int err= 0; + + const std::lock_guard lock(aria_engine::Clone_Sys::mutex_); + auto clone_hdl= aria_engine::clone_sys->find(in_loc, true); + + switch (mode) + { + case HA_CLONE_MODE_START: + err= aria_engine::clone_sys->start(true, false, clone_hdl, task_id, + in_loc); + break; + case HA_CLONE_MODE_ADD_TASK: + err= aria_engine::clone_sys->start(true, true, clone_hdl, task_id, + in_loc); + break; + case HA_CLONE_MODE_RESTART: + err=ER_NOT_SUPPORTED_YET; + my_error(ER_NOT_SUPPORTED_YET, MYF(ME_ERROR_LOG), + "ARIA SE: Clone Restart after network failure"); + break; + case HA_CLONE_MODE_VERSION: + case HA_CLONE_MODE_MAX: + err= ER_INTERNAL_ERROR; + my_error(err, MYF(ME_ERROR_LOG), "ARIA SE: Clone Begin Invalid Mode"); + DBUG_ASSERT(false); + } + if (!err && clone_hdl) + { + auto &locator= clone_hdl->get_locator(); + std::tie(loc, loc_len)= locator.get_locator(); + } + delete in_loc; + return err; +} + +static int clone_copy(THD *thd, const uchar *loc, uint loc_len, uint task_id, + Ha_clone_stage stage, Ha_clone_cbk *cbk) +{ + DBUG_ASSERT(loc); + std::unique_ptr + in_loc(new(std::nothrow) aria_engine::Locator(loc, loc_len)); + + auto clone_hdl= aria_engine::clone_sys->get(in_loc->index(), true); + int err= clone_hdl ? clone_hdl->check_error(thd) : 0; + + if (!clone_hdl || err != 0) + return err; + + return clone_hdl->clone(thd, task_id, stage, cbk); +} + +static int clone_ack(THD *, const uchar *loc, uint loc_len, + uint, int in_err, Ha_clone_cbk *) +{ + DBUG_ASSERT(loc); + std::unique_ptr + in_loc(new(std::nothrow) aria_engine::Locator(loc, loc_len)); + auto clone_hdl= aria_engine::clone_sys->get(in_loc->index(), true); + DBUG_ASSERT(clone_hdl); + if (!clone_hdl) + return 0; + clone_hdl->set_error(in_err); + return 0; +} + +static int clone_end(THD *, const uchar *loc, uint loc_len, uint task_id, + int in_err) +{ + DBUG_ASSERT(loc); + std::unique_ptr + in_loc(new(std::nothrow) aria_engine::Locator(loc, loc_len)); + auto clone_hdl= aria_engine::clone_sys->get(in_loc->index(), true); + + DBUG_ASSERT(clone_hdl); + if (!clone_hdl) + return 0; + clone_hdl->set_error(in_err); + + const std::lock_guard lock(aria_engine::Clone_Sys::mutex_); + return aria_engine::clone_sys->stop(true, clone_hdl, task_id); +} + +static int clone_apply_begin(THD *, const uchar *&loc, + uint &loc_len, uint &task_id, Ha_clone_mode mode, + const char *data_dir) +{ + aria_engine::Locator *in_loc= nullptr; + if (loc) + in_loc= new(std::nothrow) aria_engine::Locator(loc, loc_len); + int err= 0; + + const std::lock_guard lock(aria_engine::Clone_Sys::mutex_); + auto clone_hdl= aria_engine::clone_sys->find(in_loc, false); + + switch (mode) + { + case HA_CLONE_MODE_VERSION: + case HA_CLONE_MODE_START: + DBUG_ASSERT(!clone_hdl); + err= aria_engine::clone_sys->start(false, false, clone_hdl, task_id, + in_loc, data_dir); + task_id= 0; + break; + case HA_CLONE_MODE_ADD_TASK: + err= aria_engine::clone_sys->start(false, true, clone_hdl, task_id, + in_loc); + break; + case HA_CLONE_MODE_RESTART: + err=ER_NOT_SUPPORTED_YET; + my_error(ER_NOT_SUPPORTED_YET, MYF(ME_ERROR_LOG), + "ARIA SE: Clone Restart after network failure"); + break; + case HA_CLONE_MODE_MAX: + err= ER_INTERNAL_ERROR; + my_error(err, MYF(ME_ERROR_LOG), "ARIA SE: Clone Begin Invalid Mode"); + DBUG_ASSERT(false); + } + + /* While attaching tasks, don't overwrite the source locator. */ + if (!err && clone_hdl && mode != HA_CLONE_MODE_ADD_TASK) + { + auto &locator= clone_hdl->get_locator(); + std::tie(loc, loc_len)= locator.get_locator(); + } + delete in_loc; + return err; +} + +static int clone_apply(THD *thd, const uchar *loc, + uint loc_len, uint task_id, int in_err, + Ha_clone_cbk *cbk) +{ + DBUG_ASSERT(loc); + std::unique_ptr + in_loc(new(std::nothrow) aria_engine::Locator(loc, loc_len)); + + auto clone_hdl= aria_engine::clone_sys->get(in_loc->index(), false); + + DBUG_ASSERT(in_err != 0 || cbk != nullptr); + if (clone_hdl && (in_err != 0 || cbk == nullptr)) + { + clone_hdl->set_error(in_err); + my_printf_error(ER_CLONE_CLIENT_TRACE, "ARIA SE: Set Error Code %d", + MYF(ME_NOTE | ME_ERROR_LOG_ONLY), in_err); + return 0; + } + + int err= clone_hdl ? clone_hdl->check_error(thd) : 0; + if (!clone_hdl || err != 0) + return err; + + err= clone_hdl->apply(thd, task_id, cbk); + clone_hdl->set_error(err); + return err; +} + +static int clone_apply_end(THD *, const uchar *loc, uint loc_len, + uint task_id, int in_err) +{ + DBUG_ASSERT(loc); + std::unique_ptr + in_loc(new(std::nothrow) aria_engine::Locator(loc, loc_len)); + auto clone_hdl= aria_engine::clone_sys->get(in_loc->index(), false); + DBUG_ASSERT(clone_hdl); + clone_hdl->set_error(in_err); + + const std::lock_guard lock(aria_engine::Clone_Sys::mutex_); + return aria_engine::clone_sys->stop(false, clone_hdl, task_id); +} +#endif /* !EMBEDDED_LIBRARY */ + +void init_maria_clone_interfaces(handlerton *aria_hton) +{ +#ifndef EMBEDDED_LIBRARY + auto &interface= aria_hton->clone_interface; + interface.clone_capability= clone_get_capability; + + interface.clone_begin= clone_begin; + interface.clone_copy= clone_copy; + interface.clone_ack= clone_ack; + interface.clone_end= clone_end; + + interface.clone_apply_begin= clone_apply_begin; + interface.clone_apply= clone_apply; + interface.clone_apply_end= clone_apply_end; +#endif /* EMBEDDED_LIBRARY */ +} diff --git a/storage/maria/ma_pagecrc.c b/storage/maria/ma_pagecrc.c index 4e1389b1163c8..74f8ae8212b58 100644 --- a/storage/maria/ma_pagecrc.c +++ b/storage/maria/ma_pagecrc.c @@ -103,7 +103,7 @@ my_bool maria_page_crc_check(uchar *page, the CRC will be corrected at next write) */ if (no_crc_val == MARIA_NO_CRC_BITMAP_PAGE && - crc == 0 && _ma_check_if_zero(page, data_length)) + crc == 0 && !_ma_check_if_zero(page, data_length)) { DBUG_PRINT("warning", ("Found bitmap page that was not initialized")); DBUG_RETURN(0); diff --git a/storage/perfschema/pfs_server.h b/storage/perfschema/pfs_server.h index a6d0388abd093..729b4d8a369b3 100644 --- a/storage/perfschema/pfs_server.h +++ b/storage/perfschema/pfs_server.h @@ -55,7 +55,7 @@ #define PFS_MAX_SOCKET_CLASS 10 #endif #ifndef PFS_MAX_STAGE_CLASS - #define PFS_MAX_STAGE_CLASS 160 + #define PFS_MAX_STAGE_CLASS 175 #endif #ifndef PFS_STATEMENTS_STACK_SIZE #define PFS_STATEMENTS_STACK_SIZE 10 diff --git a/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result b/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result index 4b8e3802c562a..4acbb8e239089 100644 --- a/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result +++ b/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result @@ -197,6 +197,10 @@ icp_attempts icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter icp_no_match icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition does not match icp_out_of_range icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition out of range icp_match icp 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Index push-down condition matches +page_track_resets page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of resets +page_track_partial_block_writes page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of partial block writes +page_track_full_block_writes page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of full block writes +page_track_checkpoint_partial_flush_request page_track 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of partial flush requests made during checkpointing SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD; value a