/* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. 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 also distributed 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 included with MySQL. 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 "storage/ndb/plugin/ndb_share.h" #include #include #include #include "m_string.h" #include "sql/sql_class.h" #include "sql/strfunc.h" #include "sql/table.h" #include "storage/ndb/include/ndbapi/NdbEventOperation.hpp" #include "storage/ndb/plugin/ndb_conflict.h" #include "storage/ndb/plugin/ndb_event_data.h" #include "storage/ndb/plugin/ndb_log.h" #include "storage/ndb/plugin/ndb_name_util.h" #include "storage/ndb/plugin/ndb_require.h" #include "storage/ndb/plugin/ndb_table_map.h" extern Ndb *g_ndb; extern mysql_mutex_t ndbcluster_mutex; // List of NDB_SHARE's which correspond to an open table. std::unique_ptr> ndbcluster_open_tables; // List of NDB_SHARE's which have been dropped, they are kept in this list // until all references to them have been released. static std::unordered_set dropped_shares; NDB_SHARE *NDB_SHARE::create(const char *key) { if (DBUG_EVALUATE_IF("ndb_share_create_fail1", true, false)) { // Simulate failure to create NDB_SHARE return nullptr; } NDB_SHARE *share; if (!(share = (NDB_SHARE *)my_malloc(PSI_INSTRUMENT_ME, sizeof(*share), MYF(MY_WME | MY_ZEROFILL)))) return nullptr; share->flags = 0; share->state = NSS_INITIAL; /* Allocates enough space for key, db, and table_name */ share->key = NDB_SHARE::create_key(key); share->db = NDB_SHARE::key_get_db_name(share->key); share->table_name = NDB_SHARE::key_get_table_name(share->key); thr_lock_init(&share->lock); mysql_mutex_init(PSI_INSTRUMENT_ME, &share->mutex, MY_MUTEX_INIT_FAST); share->m_cfn_share = nullptr; share->op = 0; #ifndef DBUG_OFF DBUG_ASSERT(share->m_use_count == 0); share->refs = new Ndb_share_references(); #endif share->inplace_alter_new_table_def = nullptr; return share; } void NDB_SHARE::destroy(NDB_SHARE *share) { thr_lock_delete(&share->lock); mysql_mutex_destroy(&share->mutex); // ndb_index_stat_free() should have cleaned up: assert(share->index_stat_list == NULL); teardown_conflict_fn(g_ndb, share->m_cfn_share); #ifndef DBUG_OFF DBUG_ASSERT(share->m_use_count == 0); DBUG_ASSERT(share->refs->check_empty()); delete share->refs; #endif // Release memory for the variable length strings held by // key but also referenced by db, table_name and shadow_table->db etc. free_key(share->key); my_free(share); } /* Struct holding dynamic length strings for NDB_SHARE. The type is opaque to the user of NDB_SHARE and should only be accessed using NDB_SHARE accessor functions. All the strings are zero terminated. Layout: size_t key_length "key"\0 "db\0" "table_name\0" */ struct NDB_SHARE_KEY { size_t m_key_length; char m_buffer[1]; }; NDB_SHARE_KEY *NDB_SHARE::create_key(const char *new_key) { const size_t new_key_length = strlen(new_key); char db_name_buf[FN_HEADLEN]; ndb_set_dbname(new_key, db_name_buf); const size_t db_name_len = strlen(db_name_buf); char table_name_buf[FN_HEADLEN]; ndb_set_tabname(new_key, table_name_buf); const size_t table_name_len = strlen(table_name_buf); // Calculate total size needed for the variable length strings const size_t size = sizeof(NDB_SHARE_KEY) + new_key_length + db_name_len + 1 + table_name_len + 1; NDB_SHARE_KEY *allocated_key = (NDB_SHARE_KEY *)my_malloc( PSI_INSTRUMENT_ME, size, MYF(MY_WME | ME_FATALERROR)); allocated_key->m_key_length = new_key_length; // Copy key into the buffer char *buf_ptr = allocated_key->m_buffer; my_stpcpy(buf_ptr, new_key); buf_ptr += new_key_length + 1; // Copy db_name into the buffer my_stpcpy(buf_ptr, db_name_buf); buf_ptr += db_name_len + 1; // Copy table_name into the buffer my_stpcpy(buf_ptr, table_name_buf); buf_ptr += table_name_len; // Check that writing has not occurred beyond end of allocated memory assert(buf_ptr < reinterpret_cast(allocated_key) + size); DBUG_PRINT("info", ("size: %lu", (unsigned long)size)); DBUG_PRINT("info", ("new_key: '%s', %lu", new_key, (unsigned long)new_key_length)); DBUG_PRINT("info", ("db_name: '%s', %lu", db_name_buf, (unsigned long)db_name_len)); DBUG_PRINT("info", ("table_name: '%s', %lu", table_name_buf, (unsigned long)table_name_len)); DBUG_DUMP("NDB_SHARE_KEY: ", (const uchar *)allocated_key->m_buffer, size); return allocated_key; } void NDB_SHARE::free_key(NDB_SHARE_KEY *key) { my_free(key); } std::string NDB_SHARE::key_get_key(NDB_SHARE_KEY *key) { assert(key->m_key_length == strlen(key->m_buffer)); return key->m_buffer; } char *NDB_SHARE::key_get_db_name(NDB_SHARE_KEY *key) { char *buf_ptr = key->m_buffer; // Step past the key string and it's zero terminator buf_ptr += key->m_key_length + 1; return buf_ptr; } char *NDB_SHARE::key_get_table_name(NDB_SHARE_KEY *key) { char *buf_ptr = key_get_db_name(key); const size_t db_name_len = strlen(buf_ptr); // Step past the db name string and it's zero terminator buf_ptr += db_name_len + 1; return buf_ptr; } size_t NDB_SHARE::key_length() const { assert(key->m_key_length == strlen(key->m_buffer)); return key->m_key_length; } const char *NDB_SHARE::key_string() const { assert(strlen(key->m_buffer) == key->m_key_length); return key->m_buffer; } const char *NDB_SHARE::share_state_string(void) const { switch (state) { case NSS_INITIAL: return "NSS_INITIAL"; case NSS_DROPPED: return "NSS_DROPPED"; } assert(false); return ""; } void NDB_SHARE::free_share(NDB_SHARE **share) { DBUG_TRACE; mysql_mutex_assert_owner(&ndbcluster_mutex); if (!(*share)->decrement_use_count()) { // Noone is using the NDB_SHARE anymore, release it NDB_SHARE::real_free_share(share); } } NDB_SHARE *NDB_SHARE::create_and_acquire_reference(const char *key, const char *reference) { DBUG_TRACE; DBUG_PRINT("enter", ("key: '%s'", key)); mysql_mutex_assert_owner(&ndbcluster_mutex); // Make sure that the SHARE does not already exist DBUG_ASSERT(!acquire_reference_impl(key)); NDB_SHARE *share = NDB_SHARE::create(key); if (share == nullptr) { DBUG_PRINT("error", ("failed to create NDB_SHARE")); return nullptr; } // Insert the new share in list of open shares ndbcluster_open_tables->emplace(key, share); // Add share refcount from 'ndbcluster_open_tables' share->increment_use_count(); share->refs_insert("ndbcluster_open_tables"); // Add refcount for returned 'share'. share->increment_use_count(); share->refs_insert(reference); return share; } NDB_SHARE *NDB_SHARE::create_and_acquire_reference( const char *key, const class ha_ndbcluster *reference) { mysql_mutex_lock(&ndbcluster_mutex); NDB_SHARE *share = NDB_SHARE::create(key); if (share == nullptr) ndb_log_error("failed to create NDB_SHARE for key: %s", key); else { // Insert the new share in list of open shares ndbcluster_open_tables->emplace(key, share); // Add share refcount from 'ndbcluster_open_tables' share->increment_use_count(); share->refs_insert("ndbcluster_open_tables"); // Add refcount for returned 'share'. share->increment_use_count(); share->refs_insert(reference); } mysql_mutex_unlock(&ndbcluster_mutex); return share; } NDB_SHARE *NDB_SHARE::acquire_for_handler( const char *key, const class ha_ndbcluster *reference) { DBUG_TRACE; mysql_mutex_lock(&ndbcluster_mutex); NDB_SHARE *share = acquire_reference_impl(key); if (share) { share->refs_insert(reference); DBUG_PRINT("NDB_SHARE", ("'%s' reference: 'ha_ndbcluster(%p)', " "use_count: %u", share->key_string(), reference, share->use_count())); } mysql_mutex_unlock(&ndbcluster_mutex); return share; } void NDB_SHARE::release_for_handler(NDB_SHARE *share, const ha_ndbcluster *reference) { DBUG_TRACE; mysql_mutex_lock(&ndbcluster_mutex); DBUG_PRINT("NDB_SHARE", ("release '%s', reference: 'ha_ndbcluster(%p)', " "use_count: %u", share->key_string(), reference, share->use_count())); share->refs_erase(reference); NDB_SHARE::free_share(&share); mysql_mutex_unlock(&ndbcluster_mutex); } /* Acquire another reference using existing share reference. */ NDB_SHARE *NDB_SHARE::acquire_reference_on_existing(NDB_SHARE *share, const char *reference) { mysql_mutex_lock(&ndbcluster_mutex); // Should already be referenced DBUG_ASSERT(share->use_count() > 0); // Number of references should match use_count DBUG_ASSERT(share->use_count() == share->refs->size()); share->increment_use_count(); share->refs_insert(reference); DBUG_PRINT("NDB_SHARE", ("'%s', reference: '%s', use_count: %u", share->key_string(), reference, share->use_count())); mysql_mutex_unlock(&ndbcluster_mutex); return share; } /* Acquire reference using key. */ NDB_SHARE *NDB_SHARE::acquire_reference_by_key(const char *key, const char *reference) { mysql_mutex_lock(&ndbcluster_mutex); NDB_SHARE *share = acquire_reference_impl(key); if (share) { share->refs_insert(reference); DBUG_PRINT("NDB_SHARE", ("'%s', reference: '%s', use_count: %u", share->key_string(), reference, share->use_count())); } mysql_mutex_unlock(&ndbcluster_mutex); return share; } NDB_SHARE *NDB_SHARE::acquire_reference_by_key_have_lock( const char *key, const char *reference) { mysql_mutex_assert_owner(&ndbcluster_mutex); NDB_SHARE *share = acquire_reference_impl(key); if (share) { share->refs_insert(reference); DBUG_PRINT("NDB_SHARE", ("'%s', reference: '%s', use_count: %u", share->key_string(), reference, share->use_count())); } return share; } void NDB_SHARE::release_reference(NDB_SHARE *share, const char *reference) { mysql_mutex_lock(&ndbcluster_mutex); DBUG_PRINT("NDB_SHARE", ("release '%s', reference: '%s', use_count: %u", share->key_string(), reference, share->use_count())); share->refs_erase(reference); NDB_SHARE::free_share(&share); mysql_mutex_unlock(&ndbcluster_mutex); } void NDB_SHARE::release_reference_have_lock(NDB_SHARE *share, const char *reference) { mysql_mutex_assert_owner(&ndbcluster_mutex); DBUG_PRINT("NDB_SHARE", ("release '%s', reference: '%s', use_count: %u", share->key_string(), reference, share->use_count())); share->refs_erase(reference); NDB_SHARE::free_share(&share); } #ifndef DBUG_OFF bool NDB_SHARE::Ndb_share_references::check_empty() const { if (size() == 0) { // There are no references, all OK return true; } ndb_log_error( "Consistency check of NDB_SHARE references failed, the list " "of references should be empty at this time"); std::string s; debug_print(s); ndb_log_error("%s", s.c_str()); abort(); return false; } void NDB_SHARE::Ndb_share_references::debug_print( std::string &out, const char *line_separator) const { std::stringstream ss; // Print the handler list { const char *separator = ""; ss << " handlers: " << handlers.size() << " [ "; for (const auto &key : handlers) { ss << separator << "'" << key << "'"; separator = ","; } ss << " ]"; } ss << ", " << line_separator; // Print the strings list { const char *separator = ""; ss << " strings: " << strings.size() << " [ "; for (const auto &key : strings) { ss << separator << "'" << key.c_str() << "'"; separator = ","; } ss << " ]"; } ss << ", " << line_separator; out = ss.str(); } #endif void NDB_SHARE::debug_print(std::string &out, const char *line_separator) const { std::stringstream ss; ss << "NDB_SHARE { " << line_separator << " db: '" << db << "'," << line_separator << " table_name: '" << table_name << "', " << line_separator << " key: '" << key_string() << "', " << line_separator << " use_count: " << use_count() << ", " << line_separator << " state: " << share_state_string() << ", " << line_separator << " op: " << op << ", " << line_separator; #ifndef DBUG_OFF std::string refs_string; refs->debug_print(refs_string, line_separator); ss << refs_string.c_str(); // There should be as many refs as the use_count says DBUG_ASSERT(use_count() == refs->size()); #endif ss << "}"; out = ss.str(); } void NDB_SHARE::debug_print_shares(std::string &out) { std::stringstream ss; ss << "ndbcluster_open_tables {" << "\n"; for (const auto &key_and_value : *ndbcluster_open_tables) { const NDB_SHARE *share = key_and_value.second; std::string s; share->debug_print(s, "\n"); ss << s << "\n"; } ss << "}" << "\n"; out = ss.str(); } uint NDB_SHARE::decrement_use_count() { ndbcluster::ndbrequire(m_use_count > 0); return --m_use_count; } void NDB_SHARE::print_remaining_open_tables(void) { mysql_mutex_lock(&ndbcluster_mutex); if (!ndbcluster_open_tables->empty()) { std::string s; NDB_SHARE::debug_print_shares(s); std::cerr << s << std::endl; } mysql_mutex_unlock(&ndbcluster_mutex); } int NDB_SHARE::rename_share(NDB_SHARE *share, NDB_SHARE_KEY *new_key) { DBUG_TRACE; DBUG_PRINT("enter", ("share->key: '%s'", share->key_string())); DBUG_PRINT("enter", ("new_key: '%s'", NDB_SHARE::key_get_key(new_key).c_str())); mysql_mutex_lock(&ndbcluster_mutex); // Make sure that no NDB_SHARE with new_key already exists if (find_or_nullptr(*ndbcluster_open_tables, NDB_SHARE::key_get_key(new_key))) { // Dump the list of open NDB_SHARE's since new_key already exists ndb_log_error( "INTERNAL ERROR: Found existing NDB_SHARE for " "new key: '%s' while renaming: '%s'", NDB_SHARE::key_get_key(new_key).c_str(), share->key_string()); std::string s; NDB_SHARE::debug_print_shares(s); std::cerr << s << std::endl; abort(); } /* Update the share hash key. */ NDB_SHARE_KEY *old_key = share->key; share->key = new_key; ndbcluster_open_tables->erase(NDB_SHARE::key_get_key(old_key)); ndbcluster_open_tables->emplace(NDB_SHARE::key_get_key(new_key), share); // Make sure that NDB_SHARE with old key does not exist DBUG_ASSERT(find_or_nullptr(*ndbcluster_open_tables, NDB_SHARE::key_get_key(old_key)) == nullptr); // Make sure that NDB_SHARE with new key does exist DBUG_ASSERT(find_or_nullptr(*ndbcluster_open_tables, NDB_SHARE::key_get_key(new_key))); DBUG_PRINT("info", ("setting db and table_name to point at new key")); share->db = NDB_SHARE::key_get_db_name(share->key); share->table_name = NDB_SHARE::key_get_table_name(share->key); if (share->op) { Ndb_event_data *event_data = static_cast(share->op->getCustomData()); if (event_data && event_data->shadow_table) { if (!ndb_name_is_temp(share->table_name)) { DBUG_PRINT("info", ("Renaming shadow table")); // Allocate new strings for db and table_name for shadow_table // in event_data's MEM_ROOT(where the shadow_table itself is allocated) // NOTE! This causes a slight memory leak since the already existing // strings are not release until the mem_root is eventually // released. lex_string_strmake(&event_data->mem_root, &event_data->shadow_table->s->db, share->db, strlen(share->db)); lex_string_strmake(&event_data->mem_root, &event_data->shadow_table->s->table_name, share->table_name, strlen(share->table_name)); } else { DBUG_PRINT("info", ("Name is temporary, skip rename of shadow table")); // don't rename the shadow table here, it's used by injector and all // events might not have been processed. It will be dropped anyway } } } mysql_mutex_unlock(&ndbcluster_mutex); return 0; } /** Acquire NDB_SHARE for key Returns share for key, and increases the refcount on the share. @param key The key for NDB_SHARE to acquire */ NDB_SHARE *NDB_SHARE::acquire_reference_impl(const char *key) { DBUG_TRACE; DBUG_PRINT("enter", ("key: '%s'", key)); if (DBUG_EVALUATE_IF("ndb_share_acquire_fail1", true, false)) { // Simulate failure to acquire NDB_SHARE return nullptr; } mysql_mutex_assert_owner(&ndbcluster_mutex); auto it = ndbcluster_open_tables->find(key); if (it == ndbcluster_open_tables->end()) { DBUG_PRINT("error", ("%s does not exist", key)); return nullptr; } NDB_SHARE *share = it->second; // Add refcount for returned 'share'. share->increment_use_count(); return share; } void NDB_SHARE::initialize(CHARSET_INFO *charset) { ndbcluster_open_tables.reset( new collation_unordered_map(charset, PSI_INSTRUMENT_ME)); } void NDB_SHARE::deinitialize(void) { mysql_mutex_lock(&ndbcluster_mutex); // There should not be any NDB_SHARE's left -> crash after logging in debug const class Debug_require { const bool m_required_val; public: Debug_require(bool val) : m_required_val(val) {} ~Debug_require() { DBUG_ASSERT(m_required_val); } } shares_remaining(ndbcluster_open_tables->empty() && dropped_shares.empty()); // Drop remaining open shares, drop one NDB_SHARE after the other // until open tables list is empty while (!ndbcluster_open_tables->empty()) { NDB_SHARE *share = ndbcluster_open_tables->begin()->second; ndb_log_error("Still open NDB_SHARE '%s', use_count: %d, state: %s", share->key_string(), share->use_count(), share->share_state_string()); // If last ref, share is destroyed immediately, else moved to list of // dropped shares NDB_SHARE::mark_share_dropped(&share); } // Release remaining dropped shares, release one NDB_SHARE after the other // until dropped list is empty while (!dropped_shares.empty()) { NDB_SHARE *share = *dropped_shares.begin(); ndb_log_error("Not freed NDB_SHARE '%s', use_count: %d, state: %s", share->key_string(), share->use_count(), share->share_state_string()); NDB_SHARE::real_free_share(&share); } mysql_mutex_unlock(&ndbcluster_mutex); } void NDB_SHARE::release_extra_share_references(void) { mysql_mutex_lock(&ndbcluster_mutex); while (!ndbcluster_open_tables->empty()) { NDB_SHARE *share = ndbcluster_open_tables->begin()->second; /* The share kept by the server has not been freed, free it Will also take it out of _open_tables list */ DBUG_ASSERT(share->use_count() > 0); DBUG_ASSERT(share->state != NSS_DROPPED); NDB_SHARE::mark_share_dropped(&share); } mysql_mutex_unlock(&ndbcluster_mutex); } void NDB_SHARE::real_free_share(NDB_SHARE **share_ptr) { NDB_SHARE *share = *share_ptr; DBUG_TRACE; mysql_mutex_assert_owner(&ndbcluster_mutex); // Share must already be marked as dropped ndbcluster::ndbrequire(share->state == NSS_DROPPED); // Share must be in dropped list ndbcluster::ndbrequire(dropped_shares.find(share) != dropped_shares.end()); // Remove share from dropped list ndbcluster::ndbrequire(dropped_shares.erase(share)); // Remove shares reference from 'dropped_shares' share->refs_erase("dropped_shares"); NDB_SHARE::destroy(share); } extern void ndb_index_stat_free(NDB_SHARE *); void NDB_SHARE::mark_share_dropped(NDB_SHARE **share_ptr) { NDB_SHARE *share = *share_ptr; DBUG_TRACE; mysql_mutex_assert_owner(&ndbcluster_mutex); // The NDB_SHARE should not have any event operations, those // should have been removed already _before_ marking the NDB_SHARE // as dropped. DBUG_ASSERT(share->op == nullptr); if (share->state == NSS_DROPPED) { // The NDB_SHARE was already marked as dropped return; } // The index_stat is not needed anymore, free it. ndb_index_stat_free(share); // Mark share as dropped share->state = NSS_DROPPED; // Remove share from list of open shares ndbcluster::ndbrequire(ndbcluster_open_tables->erase(share->key_string())); // Remove reference from list of open shares and decrement use count share->refs_erase("ndbcluster_open_tables"); share->decrement_use_count(); // Destroy the NDB_SHARE if noone is using it, this is normally a special // case for shutdown code path. In all other cases the caller will hold // reference to the share. if (share->use_count() == 0) { NDB_SHARE::destroy(share); return; } // Someone is still using the NDB_SHARE, insert it into the list of dropped // to keep track of it until all references has been released dropped_shares.emplace(share); #ifndef DBUG_OFF std::string s; share->debug_print(s, "\n"); std::cerr << "dropped_share: " << s << std::endl; #endif // Share is referenced by 'dropped_shares' share->refs_insert("dropped_shares"); // NOTE! The refcount has not been incremented } #ifndef DBUG_OFF void NDB_SHARE::dbg_check_shares_update() { ndb_log_info("dbug_check_shares open:"); for (const auto &key_and_value : *ndbcluster_open_tables) { const NDB_SHARE *share = key_and_value.second; ndb_log_info(" %s.%s: state: %s(%u) use_count: %u", share->db, share->table_name, share->share_state_string(), (unsigned)share->state, share->use_count()); assert(share->state != NSS_DROPPED); } ndb_log_info("dbug_check_shares dropped:"); for (const NDB_SHARE *share : dropped_shares) { ndb_log_info(" %s.%s: state: %s(%u) use_count: %u", share->db, share->table_name, share->share_state_string(), (unsigned)share->state, share->use_count()); assert(share->state == NSS_DROPPED); } /** * Only shares in mysql database may be open... */ for (const auto &key_and_value : *ndbcluster_open_tables) { const NDB_SHARE *share = key_and_value.second; assert(strcmp(share->db, "mysql") == 0); } /** * Only shares in mysql database may be in dropped list... */ for (const NDB_SHARE *share : dropped_shares) { assert(strcmp(share->db, "mysql") == 0); } } #endif