From a81aec150528523a6da058f130caa69d1b7e378b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Fri, 23 Apr 2021 16:58:25 +0300 Subject: [PATCH] MDEV-25491 preparation: Clean up tablespace destruction fil_check_pending_ops(), fil_check_pending_io(): Remove. These functions were actually duplicating each other ever since commit 118e258aaac5da75a2ac4556201aaea3688fac67 (MDEV-23855). fil_space_t::check_pending_operations(): Replaces fil_check_pending_operations() and incorporates the logic of fil_check_pending_ops(). Avoid unnecessary lookups for the tablespace. Just wait for the reference count to drop to zero. fil_space_t::io(): Remove an unnecessary condition. We can (and probably better should) refuse asynchronous reads of undo tablespaces that are being truncated. fil_truncate_prepare(): Remove. trx_purge_truncate_history(): Implement the necessary steps that used to be in fil_truncate_prepare(). --- storage/innobase/fil/fil0fil.cc | 270 ++++++++--------------------- storage/innobase/include/fil0fil.h | 12 +- storage/innobase/trx/trx0purge.cc | 45 +++-- 3 files changed, 110 insertions(+), 217 deletions(-) diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc index c96c5f2d595c8..947076f9b4a00 100644 --- a/storage/innobase/fil/fil0fil.cc +++ b/storage/innobase/fil/fil0fil.cc @@ -1561,149 +1561,43 @@ fil_name_write( mtr->log_file_op(FILE_MODIFY, space_id, name); } -/** Check for pending operations. -@param[in] space tablespace -@param[in] count number of attempts so far -@return 0 if no operations else count + 1. */ -static ulint fil_check_pending_ops(const fil_space_t* space, ulint count) -{ - mysql_mutex_assert_owner(&fil_system.mutex); - - if (!space) { - return 0; - } - - if (auto n_pending_ops = space->referenced()) { - - /* Give a warning every 10 second, starting after 1 second */ - if ((count % 500) == 50) { - ib::warn() << "Trying to delete" - " tablespace '" << space->chain.start->name - << "' but there are " << n_pending_ops - << " pending operations on it."; - } - - return(count + 1); - } - - return(0); -} - -/*******************************************************************//** -Check for pending IO. -@return 0 if no pending else count + 1. */ -static -ulint -fil_check_pending_io( -/*=================*/ - fil_space_t* space, /*!< in/out: Tablespace to check */ - fil_node_t** node, /*!< out: Node in space list */ - ulint count) /*!< in: number of attempts so far */ +fil_space_t *fil_space_t::check_pending_operations(ulint id) { - mysql_mutex_assert_owner(&fil_system.mutex); - - /* The following code must change when InnoDB supports - multiple datafiles per tablespace. */ - ut_ad(UT_LIST_GET_LEN(space->chain) == 1); - - *node = UT_LIST_GET_FIRST(space->chain); - - if (const uint32_t p = space->referenced()) { - ut_a(!(*node)->being_extended); - - /* Give a warning every 10 second, starting after 1 second */ - if ((count % 500) == 50) { - ib::info() << "Trying to delete" - " tablespace '" << space->chain.start->name - << "' but there are " << p - << " pending i/o's on it."; - } - - return(count + 1); - } - - return(0); -} - -/*******************************************************************//** -Check pending operations on a tablespace. -@return tablespace */ -static -fil_space_t* -fil_check_pending_operations( -/*=========================*/ - ulint id, /*!< in: space id */ - bool truncate, /*!< in: whether to truncate a file */ - char** path) /*!< out/own: tablespace path */ -{ - ulint count = 0; - - ut_a(!is_system_tablespace(id)); - mysql_mutex_lock(&fil_system.mutex); - fil_space_t* sp = fil_space_get_by_id(id); - - if (sp) { - sp->set_stopping(true); - if (sp->crypt_data) { - sp->reacquire(); - mysql_mutex_unlock(&fil_system.mutex); - fil_space_crypt_close_tablespace(sp); - mysql_mutex_lock(&fil_system.mutex); - sp->release(); - } - } - - /* Check for pending operations. */ - - do { - count = fil_check_pending_ops(sp, count); - - mysql_mutex_unlock(&fil_system.mutex); - - if (count) { - std::this_thread::sleep_for( - std::chrono::milliseconds(20)); - } else if (!sp) { - return nullptr; - } - - mysql_mutex_lock(&fil_system.mutex); - - sp = fil_space_get_by_id(id); - } while (count); - - /* Check for pending IO. */ - - for (;;) { - if (truncate) { - sp->is_being_truncated = true; - } - - fil_node_t* node; - - count = fil_check_pending_io(sp, &node, count); - - if (count == 0 && path) { - *path = mem_strdup(node->name); - } - - mysql_mutex_unlock(&fil_system.mutex); + ut_a(!is_system_tablespace(id)); + mysql_mutex_lock(&fil_system.mutex); + fil_space_t *space= fil_space_get_by_id(id); - if (count == 0) { - break; - } + if (space) + { + const uint32_t n= space->acquire_low(); + ut_ad(!(n & STOPPING)); - std::this_thread::sleep_for(std::chrono::milliseconds(20)); - mysql_mutex_lock(&fil_system.mutex); - sp = fil_space_get_by_id(id); + if (space->crypt_data) + { + mysql_mutex_unlock(&fil_system.mutex); + fil_space_crypt_close_tablespace(space); + mysql_mutex_lock(&fil_system.mutex); + } + space->set_stopping(true); + space->release(); + } + mysql_mutex_unlock(&fil_system.mutex); - if (!sp) { - mysql_mutex_unlock(&fil_system.mutex); - break; - } - } + if (!space) + return nullptr; - return sp; + for (ulint count= 0;; count++) + { + auto pending= space->referenced(); + if (!pending) + return space; + /* Give a warning every 10 second, starting after 1 second */ + if ((count % 500) == 50) + ib::warn() << "Trying to delete tablespace '" + << space->chain.start->name << "' but there are " + << pending << " pending operations on it."; + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } } /** Close a single-table tablespace on failed IMPORT TABLESPACE. @@ -1712,8 +1606,7 @@ Free all pages used by the tablespace. */ void fil_close_tablespace(ulint id) { ut_ad(!is_system_tablespace(id)); - char* path = nullptr; - fil_space_t* space = fil_check_pending_operations(id, false, &path); + fil_space_t* space = fil_space_t::check_pending_operations(id); if (!space) { return; } @@ -1730,23 +1623,22 @@ void fil_close_tablespace(ulint id) os_aio_wait_until_no_pending_writes(); ut_ad(space->is_stopping()); - /* If the free is successful, the wrlock will be released before - the space memory data structure is freed. */ - - if (!fil_space_free(id, true)) { - space->x_unlock(); - } - /* If it is a delete then also delete any generated files, otherwise when we drop the database the remove directory will fail. */ - if (char* cfg_name = fil_make_filepath(path, fil_space_t::name_type{}, + if (char* cfg_name = fil_make_filepath(space->chain.start->name, + fil_space_t::name_type{}, CFG, false)) { os_file_delete_if_exists(innodb_data_file_key, cfg_name, NULL); ut_free(cfg_name); } - ut_free(path); + /* If the free is successful, the wrlock will be released before + the space memory data structure is freed. */ + + if (!fil_space_free(id, true)) { + space->x_unlock(); + } } /** Delete a tablespace and associated .ibd file. @@ -1757,12 +1649,11 @@ void fil_close_tablespace(ulint id) dberr_t fil_delete_tablespace(ulint id, bool if_exists, std::vector* detached_handles) { - char* path = NULL; ut_ad(!is_system_tablespace(id)); ut_ad(!detached_handles || detached_handles->empty()); dberr_t err; - fil_space_t *space = fil_check_pending_operations(id, false, &path); + fil_space_t *space = fil_space_t::check_pending_operations(id); if (!space) { err = DB_TABLESPACE_NOT_FOUND; @@ -1771,8 +1662,9 @@ dberr_t fil_delete_tablespace(ulint id, bool if_exists, << " because it is not found" " in the tablespace memory cache."; } - - goto func_exit; +func_exit: + ibuf_delete_for_discarded_space(id); + return err; } /* IMPORTANT: Because we have set space::stop_new_ops there @@ -1808,7 +1700,7 @@ dberr_t fil_delete_tablespace(ulint id, bool if_exists, mtr_t mtr; mtr.start(); - mtr.log_file_op(FILE_DELETE, id, path); + mtr.log_file_op(FILE_DELETE, id, space->chain.start->name); mtr.commit(); /* Even if we got killed shortly after deleting the tablespace file, the record must have already been @@ -1816,7 +1708,8 @@ dberr_t fil_delete_tablespace(ulint id, bool if_exists, log_write_up_to(mtr.commit_lsn(), true); if (char* cfg_name = fil_make_filepath( - path, fil_space_t::name_type{}, CFG, false)) { + space->chain.start->name, + fil_space_t::name_type{}, CFG, false)) { os_file_delete_if_exists(innodb_data_file_key, cfg_name, nullptr); ut_free(cfg_name); @@ -1832,54 +1725,34 @@ dberr_t fil_delete_tablespace(ulint id, bool if_exists, /* Double check the sanity of pending ops after reacquiring the fil_system::mutex. */ - if (const fil_space_t* s = fil_space_get_by_id(id)) { - ut_a(s == space); - ut_a(!space->referenced()); - ut_a(UT_LIST_GET_LEN(space->chain) == 1); - auto handles = fil_system.detach(space, - detached_handles != nullptr); - if (detached_handles) { - *detached_handles = std::move(handles); - } - mysql_mutex_unlock(&fil_system.mutex); - - mysql_mutex_lock(&log_sys.mutex); - - if (space->max_lsn != 0) { - ut_d(space->max_lsn = 0); - fil_system.named_spaces.remove(*space); - } + ut_a(space == fil_space_get_by_id(id)); + ut_a(!space->referenced()); + ut_a(UT_LIST_GET_LEN(space->chain) == 1); + auto handles = fil_system.detach(space, detached_handles != nullptr); + if (detached_handles) { + *detached_handles = std::move(handles); + } + mysql_mutex_unlock(&fil_system.mutex); - mysql_mutex_unlock(&log_sys.mutex); - fil_space_free_low(space); + mysql_mutex_lock(&log_sys.mutex); - if (!os_file_delete(innodb_data_file_key, path) - && !os_file_delete_if_exists( - innodb_data_file_key, path, NULL)) { + if (space->max_lsn != 0) { + ut_d(space->max_lsn = 0); + fil_system.named_spaces.remove(*space); + } - /* Note: This is because we have removed the - tablespace instance from the cache. */ + mysql_mutex_unlock(&log_sys.mutex); - err = DB_IO_ERROR; - } - } else { - mysql_mutex_unlock(&fil_system.mutex); - err = DB_TABLESPACE_NOT_FOUND; + if (!os_file_delete(innodb_data_file_key, space->chain.start->name) + && !os_file_delete_if_exists(innodb_data_file_key, + space->chain.start->name, NULL)) { + /* Note: This is because we have removed the + tablespace instance from the cache. */ + err = DB_IO_ERROR; } -func_exit: - ut_free(path); - ibuf_delete_for_discarded_space(id); - return(err); -} - -/** Prepare to truncate an undo tablespace. -@param[in] space_id undo tablespace id -@return the tablespace -@retval NULL if tablespace not found */ -fil_space_t *fil_truncate_prepare(ulint space_id) -{ - return fil_check_pending_operations(space_id, true, nullptr); + fil_space_free_low(space); + goto func_exit; } /*******************************************************************//** @@ -3090,8 +2963,7 @@ fil_io_t fil_space_t::io(const IORequest &type, os_offset_t offset, size_t len, fil_node_t* node= UT_LIST_GET_FIRST(chain); ut_ad(node); - if (type.type == IORequest::READ_ASYNC && is_stopping() - && !is_being_truncated) { + if (type.type == IORequest::READ_ASYNC && is_stopping()) { release(); return {DB_TABLESPACE_DELETED, nullptr}; } diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h index 95009bc0bc103..bd953566d232b 100644 --- a/storage/innobase/include/fil0fil.h +++ b/storage/innobase/include/fil0fil.h @@ -517,6 +517,12 @@ struct fil_space_t final /** Note that operations on the tablespace must stop or can resume */ inline void set_stopping(bool stopping); + /** Look up the tablespace and wait for pending operations to cease + @param id tablespace identifier + @return tablespace + @retval nullptr if no tablespace was found */ + static fil_space_t *check_pending_operations(ulint id); + private: MY_ATTRIBUTE((warn_unused_result)) /** Try to acquire a tablespace reference. @@ -1580,12 +1586,6 @@ dberr_t fil_delete_tablespace(ulint id, bool if_exists= false, std::vector *detached_handles= nullptr); -/** Prepare to truncate an undo tablespace. -@param[in] space_id undo tablespace id -@return the tablespace -@retval NULL if the tablespace does not exist */ -fil_space_t* fil_truncate_prepare(ulint space_id); - /** Close a single-table tablespace on failed IMPORT TABLESPACE. The tablespace must be cached in the memory cache. Free all pages used by the tablespace. */ diff --git a/storage/innobase/trx/trx0purge.cc b/storage/innobase/trx/trx0purge.cc index 9d248061c593c..4ebab338996a6 100644 --- a/storage/innobase/trx/trx0purge.cc +++ b/storage/innobase/trx/trx0purge.cc @@ -605,7 +605,7 @@ static void trx_purge_truncate_history() return; } - const fil_space_t& space = *purge_sys.truncate.current; + fil_space_t& space = *purge_sys.truncate.current; /* Undo tablespace always are a single file. */ ut_a(UT_LIST_GET_LEN(space.chain) == 1); fil_node_t* file = UT_LIST_GET_FIRST(space.chain); @@ -685,26 +685,47 @@ static void trx_purge_truncate_history() log_free_check(); - /* Adjust the tablespace metadata. */ - if (!fil_truncate_prepare(space.id)) { - ib::error() << "Failed to find UNDO tablespace " - << file->name; - return; - } - /* Re-initialize tablespace, in a single mini-transaction. */ mtr_t mtr; const ulint size = SRV_UNDO_TABLESPACE_SIZE_IN_PAGES; mtr.start(); - mtr.x_lock_space(purge_sys.truncate.current); + mtr.x_lock_space(&space); + + /* Adjust the tablespace metadata. */ + mysql_mutex_lock(&fil_system.mutex); + space.set_stopping(true); + space.is_being_truncated = true; + if (space.crypt_data) { + space.reacquire(); + mysql_mutex_unlock(&fil_system.mutex); + fil_space_crypt_close_tablespace(&space); + space.release(); + } else { + mysql_mutex_unlock(&fil_system.mutex); + } + + uint i = 60; + + while (space.referenced()) { + if (!--i) { + mtr.commit(); + ib::error() << "Failed to freeze" + " UNDO tablespace " + << file->name; + return; + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + /* Associate the undo tablespace with mtr. During mtr::commit(), InnoDB can use the undo tablespace object to clear all freed ranges */ - mtr.set_named_space(purge_sys.truncate.current); + mtr.set_named_space(&space); mtr.trim_pages(page_id_t(space.id, size)); - fsp_header_init(purge_sys.truncate.current, size, &mtr); + fsp_header_init(&space, size, &mtr); mysql_mutex_lock(&fil_system.mutex); - purge_sys.truncate.current->size = file->size = size; + space.size = file->size = size; mysql_mutex_unlock(&fil_system.mutex); buf_block_t* sys_header = trx_sysf_get(&mtr);