1015 lines
29 KiB
C++
1015 lines
29 KiB
C++
/* Copyright (c) 2018, 2021, Alibaba 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/PolarDB-X Engine 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/PolarDB-X Engine.
|
|
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/ccl/ccl.h"
|
|
#include "errmsg.h"
|
|
#include "mysql/plugin.h"
|
|
#include "mysqld_error.h"
|
|
#include "mysys_err.h"
|
|
#include "sql/ccl/ccl_common.h"
|
|
#include "sql/ccl/ccl_hint.h"
|
|
#include "sql/current_thd.h"
|
|
#include "sql/derror.h" // ER_THD
|
|
#include "sql/sql_class.h"
|
|
#include "sql/sql_error.h"
|
|
#include "sql/sql_lex.h"
|
|
#include "sql/table.h"
|
|
#include "sql/mysqld_thd_manager.h"
|
|
|
|
/**
|
|
Concurrency control module.
|
|
*/
|
|
|
|
namespace im {
|
|
|
|
/* The max timeout when concurrency control */
|
|
ulong ccl_wait_timeout = CCL_LONG_WAIT;
|
|
|
|
/* The max waiting count when concurrency control */
|
|
ulonglong ccl_max_waiting_count = CCL_DEFAULT_WAITING_COUNT;
|
|
|
|
/**
|
|
Comply the rule when execute the statement.
|
|
|
|
@param[in] sql_command Query command
|
|
@param[in] all_tables Table list
|
|
@param[in] query Statement string
|
|
|
|
@retval true Rule worked
|
|
@retval false Skipped
|
|
*/
|
|
bool Ccl_comply::comply_rule(enum_sql_command sql_command,
|
|
TABLE_LIST *all_tables, const LEX_CSTRING &query) {
|
|
Ccl_rule_type rule_type;
|
|
Ccl_rule_set *rule_set;
|
|
ulonglong version;
|
|
Ccl_slot *slot;
|
|
DBUG_ENTER("Ccl_comply::comply_rule");
|
|
|
|
/** Already comply some rule */
|
|
if (m_slot != nullptr) DBUG_RETURN(false);
|
|
|
|
DBUG_ASSERT(m_slot == nullptr && m_version == 0);
|
|
|
|
rule_type = sql_command_to_ccl_type(sql_command);
|
|
|
|
if (rule_type == Ccl_rule_type::RULE_UNKNOWN) DBUG_RETURN(false);
|
|
|
|
rule_set = System_ccl::instance()->get_rule_set(rule_type);
|
|
DBUG_ASSERT(rule_set);
|
|
|
|
/* The version value is retrieved under lock protecting */
|
|
if ((slot = rule_set->comply_rule(all_tables, query, &version))) {
|
|
m_version = version;
|
|
m_slot = slot;
|
|
m_slot->enter_cond(m_thd, m_version);
|
|
DBUG_RETURN(true);
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
/**
|
|
Comply the queue hint when execute the statement.
|
|
|
|
@param[in] thd Thread context
|
|
@param[in] lex Lex context
|
|
@param[in] hint Queue hint
|
|
|
|
@retval true Rule worked
|
|
@retval false Skipped
|
|
*/
|
|
bool Ccl_comply::comply_queue(THD *thd, LEX *lex, Ccl_queue_hint *hint) {
|
|
Ccl_rule_set *rule_set;
|
|
ulonglong version;
|
|
Ccl_slot *slot;
|
|
ulonglong hash_value;
|
|
DBUG_ENTER("Ccl_comply::comply_queue");
|
|
|
|
/** Already comply some rule */
|
|
if (m_slot != nullptr) DBUG_RETURN(false);
|
|
|
|
DBUG_ASSERT(m_slot == nullptr && m_version == 0);
|
|
|
|
hash_value = hint->hash_value(thd, lex->select_lex);
|
|
rule_set = System_ccl::instance()->get_queue_buckets();
|
|
DBUG_ASSERT(rule_set);
|
|
|
|
/* The version value is retrieved under lock protecting */
|
|
if ((slot = rule_set->comply_queue(hash_value, &version))) {
|
|
m_version = version;
|
|
m_slot = slot;
|
|
m_slot->enter_cond(m_thd, m_version);
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
End the statement if comply some rule.
|
|
*/
|
|
void Ccl_comply::comply_end() {
|
|
if (m_slot) {
|
|
m_slot->exit_cond();
|
|
m_slot = nullptr;
|
|
m_version = 0;
|
|
}
|
|
}
|
|
|
|
/* Destructor */
|
|
Ccl_comply::~Ccl_comply() {
|
|
DBUG_ENTER("Ccl_comply::~Ccl_comply");
|
|
DBUG_ASSERT(m_slot == nullptr && m_version == 0);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Whether the query string match the keywords.
|
|
|
|
@param[in] query Statement
|
|
@param[in] ordered Match the keyword orderly or not
|
|
|
|
@retval true Matched
|
|
@retval false Not matched
|
|
*/
|
|
bool Ccl_keywords::match(const LEX_CSTRING &query, bool ordered) const {
|
|
std::string target(query.str);
|
|
std::string::size_type pos = 0;
|
|
std::string::size_type sub_pos = 0;
|
|
|
|
for (Keyword_array::const_iterator it = m_keyword_array.cbegin();
|
|
it != m_keyword_array.cend(); it++) {
|
|
if ((sub_pos = target.find(it->c_str(), pos)) != std::string::npos) {
|
|
if (ordered) pos = sub_pos;
|
|
} else
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
Build the keyword array from string
|
|
|
|
@param[in] source string
|
|
*/
|
|
void Ccl_keywords::build_keyword(const char *str) {
|
|
std::string::size_type pos1, pos2;
|
|
DBUG_ENTER("Ccl_keywords::build_keyword");
|
|
DBUG_ASSERT(m_keyword_array.size() == 0);
|
|
|
|
if (str == nullptr || str[0] == '\0') DBUG_VOID_RETURN;
|
|
|
|
String_ccl t_str(str);
|
|
pos1 = 0;
|
|
pos2 = t_str.find(SEPARATOR);
|
|
while (pos2 != std::string::npos) {
|
|
m_keyword_array.push_back(t_str.substr(pos1, pos2 - pos1));
|
|
|
|
pos1 = pos2 + strlen(SEPARATOR);
|
|
pos2 = t_str.find(SEPARATOR, pos1);
|
|
}
|
|
m_keyword_array.push_back(t_str.substr(pos1));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Search an available slot for rule.
|
|
report error message by level if failed.
|
|
|
|
@param[in] level error level
|
|
|
|
@retval true Failure
|
|
@retval false Success
|
|
*/
|
|
bool Ccl_rule::bind_slot(Ccl_error_level level) {
|
|
DBUG_ENTER("Ccl_rule::bind_slot");
|
|
DBUG_ASSERT(m_slot == nullptr);
|
|
|
|
Ccl_slot *slot =
|
|
Ccl_ring_slots::instance()->acquire_slot(m_concurrency_count);
|
|
if (slot) {
|
|
m_slot = slot;
|
|
DBUG_RETURN(false);
|
|
} else {
|
|
if (level == Ccl_error_level::CCL_CRITICAL) {
|
|
my_error(ER_CCL_UNAVAILABLE_SLOT, MYF(0), m_rule_id);
|
|
} else {
|
|
push_warning_printf(
|
|
current_thd, Sql_condition::SL_WARNING, ER_CCL_UNAVAILABLE_SLOT,
|
|
ER_THD(current_thd, ER_CCL_UNAVAILABLE_SLOT), m_rule_id);
|
|
}
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Release the slot back to ring_buffer,
|
|
Call it when destruct rule.
|
|
*/
|
|
void Ccl_rule::unbind_slot() {
|
|
DBUG_ENTER("Ccl_rule::unbind_slot");
|
|
if (m_slot) {
|
|
m_slot->release();
|
|
m_slot = nullptr;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
bool Ccl_rule::match_keyword(const LEX_CSTRING &query) const {
|
|
if (m_ccl_keywords.size() == 0 || query.length == 0) return false;
|
|
|
|
return m_ccl_keywords.match(query, (m_order == Ccl_rule_ordered::RULE_ORDER));
|
|
}
|
|
|
|
/* Clear intact container */
|
|
void Ccl_rule_set::clear_intact_rules() {
|
|
m_intact_size.store(0);
|
|
/* Clear Intact table rules */
|
|
for (Intact_object_rules::const_iterator it = m_intact_rules.cbegin();
|
|
it != m_intact_rules.cend(); it++) {
|
|
destroy_object<Ccl_rule>(const_cast<Ccl_rule *>(it->second));
|
|
}
|
|
m_intact_rules.clear();
|
|
}
|
|
|
|
/**
|
|
clear the rule from intact container by rule_id.
|
|
|
|
@param[in] rule_id rule id
|
|
|
|
@retval affected rows
|
|
*/
|
|
size_t Ccl_rule_set::delete_intact_rule(ulonglong rule_id) {
|
|
size_t num = 0;
|
|
/* delete Intact table rules */
|
|
for (Intact_object_rules::const_iterator it = m_intact_rules.cbegin();
|
|
it != m_intact_rules.cend();) {
|
|
const Ccl_rule *rule = it->second;
|
|
if (rule->get_rule_id() == rule_id) {
|
|
destroy_object<Ccl_rule>(const_cast<Ccl_rule *>(rule));
|
|
it = m_intact_rules.erase(it);
|
|
num += 1;
|
|
} else
|
|
it++;
|
|
}
|
|
m_intact_size.store(m_intact_rules.size());
|
|
return num;
|
|
}
|
|
|
|
/* Clear keyword container */
|
|
void Ccl_rule_set::clear_keyword_rules() {
|
|
m_keyword_size.store(0);
|
|
/* Clear keyword rules */
|
|
for (Keyword_rules::const_iterator it = m_keyword_rules.cbegin();
|
|
it != m_keyword_rules.cend(); it++) {
|
|
destroy_object<Ccl_rule>(*it);
|
|
}
|
|
m_keyword_rules.clear();
|
|
}
|
|
/**
|
|
clear the rule from keyword container by rule_id.
|
|
|
|
@param[in] rule_id rule id
|
|
|
|
@retval affected rows
|
|
*/
|
|
size_t Ccl_rule_set::delete_keyword_rule(ulonglong rule_id) {
|
|
size_t num = 0;
|
|
for (Keyword_rules::const_iterator it = m_keyword_rules.cbegin();
|
|
it != m_keyword_rules.cend();) {
|
|
const Ccl_rule *rule = *it;
|
|
if (rule->get_rule_id() == rule_id) {
|
|
destroy_object<Ccl_rule>(const_cast<Ccl_rule *>(rule));
|
|
m_keyword_rules.erase(it);
|
|
num += 1;
|
|
} else
|
|
it++;
|
|
}
|
|
m_keyword_size.store(m_keyword_rules.size());
|
|
return num;
|
|
}
|
|
|
|
/**
|
|
clear the rule from command container by rule_id.
|
|
|
|
@param[in] rule_id rule id
|
|
|
|
@retval affected rows
|
|
*/
|
|
size_t Ccl_rule_set::delete_command_rule(ulonglong rule_id) {
|
|
size_t num = 0;
|
|
if (m_command_rule && m_command_rule->get_rule_id() == rule_id) {
|
|
m_command_size.store(0);
|
|
destroy_object<Ccl_rule>(m_command_rule);
|
|
m_command_rule = nullptr;
|
|
num += 1;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
/* Clear command container */
|
|
void Ccl_rule_set::clear_command_rule() {
|
|
m_command_size.store(0);
|
|
if (m_command_rule) destroy_object<Ccl_rule>(m_command_rule);;
|
|
m_command_rule = nullptr;
|
|
}
|
|
|
|
/**
|
|
Clear all rules from intact, keyword, command container.
|
|
*/
|
|
void Ccl_rule_set::clear_rules() {
|
|
Rule_lock_helper intact_lock(&m_intact_lock, true);
|
|
DBUG_ASSERT(intact_lock.effect());
|
|
clear_intact_rules();
|
|
intact_lock.unlock();
|
|
|
|
Rule_lock_helper keyword_lock(&m_keyword_lock, true);
|
|
DBUG_ASSERT(keyword_lock.effect());
|
|
clear_keyword_rules();
|
|
keyword_lock.unlock();
|
|
|
|
Rule_lock_helper command_lock(&m_command_lock, true);
|
|
DBUG_ASSERT(command_lock.effect());
|
|
clear_command_rule();
|
|
command_lock.unlock();
|
|
}
|
|
|
|
/**
|
|
Clear all the queue bucket.
|
|
*/
|
|
void Ccl_rule_set::clear_queue_buckets(bool need_lock) {
|
|
if (need_lock) mysql_rwlock_wrlock(&m_queue_lock);
|
|
|
|
m_queue_size.store(0);
|
|
for (Queue_buckets::const_iterator it = m_queue_buckets.cbegin();
|
|
it != m_queue_buckets.cend(); it++) {
|
|
destroy_object<Ccl_rule>(*it);
|
|
}
|
|
m_queue_buckets.clear();
|
|
|
|
if (need_lock) mysql_rwlock_unlock(&m_queue_lock);
|
|
}
|
|
|
|
/**
|
|
Init the queue buckets
|
|
|
|
@param[in] bucket_count The bucket count
|
|
@param[in] bucket_size The bucket size
|
|
@param[in] level Error level
|
|
|
|
@retval successful item count
|
|
*/
|
|
size_t Ccl_rule_set::init_queue_buckets(ulonglong bucket_count,
|
|
ulonglong bucket_size,
|
|
Ccl_error_level level) {
|
|
size_t num = 0;
|
|
Rule_lock_helper queue_lock(&m_queue_lock, true);
|
|
DBUG_ASSERT(queue_lock.effect());
|
|
clear_queue_buckets(false);
|
|
|
|
for (ulonglong i = 0; i < bucket_count; i++) {
|
|
Ccl_rule *rule = allocate_ccl_object<Ccl_rule>(bucket_size, key_memory_ccl);
|
|
if (rule->bind_slot(level)) {
|
|
destroy_object<Ccl_rule>(rule);
|
|
continue;
|
|
}
|
|
/* Ccl queue bucket rule id is sequenced internally */
|
|
rule->set_id(i + 1);
|
|
m_queue_buckets.push_back(rule);
|
|
num += 1;
|
|
}
|
|
m_queue_size.store(m_queue_buckets.size());
|
|
return num;
|
|
}
|
|
|
|
/**
|
|
Clear the rule cache by rule_id.
|
|
|
|
@param[in] rule_id rule id
|
|
|
|
@retval affected rows
|
|
*/
|
|
size_t Ccl_rule_set::delete_rule(ulonglong rule_id) {
|
|
size_t num = 0;
|
|
Rule_lock_helper intact_lock(&m_intact_lock, true);
|
|
DBUG_ASSERT(intact_lock.effect());
|
|
num += delete_intact_rule(rule_id);
|
|
intact_lock.unlock();
|
|
|
|
Rule_lock_helper keyword_lock(&m_keyword_lock, true);
|
|
DBUG_ASSERT(keyword_lock.effect());
|
|
num += delete_keyword_rule(rule_id);
|
|
keyword_lock.unlock();
|
|
|
|
Rule_lock_helper command_lock(&m_command_lock, true);
|
|
DBUG_ASSERT(command_lock.effect());
|
|
num += delete_command_rule(rule_id);
|
|
command_lock.unlock();
|
|
|
|
return num;
|
|
}
|
|
|
|
/**
|
|
Refresh the rules.
|
|
Report the error message by level.
|
|
|
|
Immediate break off insert rules if error_level is critical,
|
|
and insert_xxx_rules function should has report my_error.
|
|
For example: add_ccl_rule()
|
|
|
|
Continue to insert rules if error_level is warning,
|
|
and insert_xxx_rules function maybe has pushed warning message.
|
|
For example: flush_ccl_rule()
|
|
|
|
@param[in] group rule group
|
|
@param[in] force_clean whether clear cache
|
|
@param[in] level error level
|
|
|
|
@retval added rule count
|
|
*/
|
|
size_t Ccl_rule_set::refresh_rules(Ccl_record_group &group, bool force_clean,
|
|
Ccl_error_level level) {
|
|
size_t num = 0;
|
|
DBUG_ENTER("Ccl_rule_set::refresh_rules");
|
|
|
|
if (group.intact_rules.size() > 0) {
|
|
Rule_lock_helper intact_lock(&m_intact_lock, true);
|
|
DBUG_ASSERT(intact_lock.effect());
|
|
if (force_clean) clear_intact_rules();
|
|
size_t intact_num = insert_intact_rules(group.intact_rules, level);
|
|
intact_lock.unlock();
|
|
num += intact_num;
|
|
if (level == Ccl_error_level::CCL_CRITICAL &&
|
|
intact_num != group.intact_rules.size()) {
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (group.keyword_rules.size() > 0) {
|
|
Rule_lock_helper keyword_lock(&m_keyword_lock, true);
|
|
DBUG_ASSERT(keyword_lock.effect());
|
|
if (force_clean) clear_keyword_rules();
|
|
size_t keyword_num = insert_keyword_rules(group.keyword_rules, level);
|
|
keyword_lock.unlock();
|
|
num += keyword_num;
|
|
if (level == Ccl_error_level::CCL_CRITICAL &&
|
|
keyword_num != group.keyword_rules.size()) {
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (group.command_rules.size() > 0) {
|
|
Rule_lock_helper command_lock(&m_command_lock, true);
|
|
DBUG_ASSERT(command_lock.effect());
|
|
if (force_clean) clear_command_rule();
|
|
size_t command_num = insert_command_rules(group.command_rules, level);
|
|
command_lock.unlock();
|
|
num += command_num;
|
|
if (level == Ccl_error_level::CCL_CRITICAL &&
|
|
command_num != group.command_rules.size()) {
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
end:
|
|
DBUG_RETURN(num);
|
|
}
|
|
|
|
/**
|
|
Insert the intact rules into cache.
|
|
Report corressponding error message by level.
|
|
|
|
@param[in] rules Rule set
|
|
@param[in] level Error level
|
|
|
|
@retval affected rule count
|
|
*/
|
|
size_t Ccl_rule_set::insert_intact_rules(std::vector<Ccl_record *> &rules,
|
|
Ccl_error_level level) {
|
|
size_t num = 0;
|
|
Ccl_rule *rule = nullptr;
|
|
DBUG_ENTER("Ccl_rule_set::insert_intact_rules");
|
|
|
|
for (auto it = rules.cbegin(); it != rules.cend(); ++it) {
|
|
Ccl_record *record = *it;
|
|
rule = allocate_ccl_object<Ccl_rule>(record, key_memory_ccl);
|
|
|
|
/* Bind the slot, break off if critical */
|
|
if (rule->bind_slot(level)) {
|
|
destroy_object<Ccl_rule>(rule);
|
|
if (level == Ccl_error_level::CCL_CRITICAL)
|
|
break;
|
|
else
|
|
continue;
|
|
}
|
|
|
|
if (!(m_intact_rules
|
|
.insert(Intact_value_type(
|
|
Intact_key_type(rule->get_schema(), rule->get_table()), rule))
|
|
.second)) {
|
|
if (level == Ccl_error_level::CCL_CRITICAL) {
|
|
my_error(ER_CCL_DUPLICATE_RULE, MYF(0), rule->get_rule_id(),
|
|
"load intact rule");
|
|
destroy_object<Ccl_rule>(rule);
|
|
break;
|
|
} else {
|
|
push_warning_printf(current_thd, Sql_condition::SL_WARNING,
|
|
ER_CCL_DUPLICATE_RULE,
|
|
ER_THD(current_thd, ER_CCL_DUPLICATE_RULE),
|
|
rule->get_rule_id(), "load intact rule");
|
|
destroy_object<Ccl_rule>(rule);
|
|
}
|
|
} else
|
|
num += 1;
|
|
}
|
|
m_intact_size.store(m_intact_rules.size());
|
|
DBUG_RETURN(num);
|
|
}
|
|
/**
|
|
Insert the intact rules into cache.
|
|
Report corressponding error message by level.
|
|
|
|
@param[in] rules Rule set
|
|
@param[in] level Error level
|
|
|
|
@retval affected rule count
|
|
*/
|
|
size_t Ccl_rule_set::insert_keyword_rules(std::vector<Ccl_record *> &rules,
|
|
Ccl_error_level level) {
|
|
size_t num = 0;
|
|
Ccl_rule *rule = nullptr;
|
|
DBUG_ENTER("Ccl_rule_set::insert_keyword_rules");
|
|
for (auto it = rules.cbegin(); it != rules.cend(); ++it) {
|
|
Ccl_record *record = *it;
|
|
rule = allocate_ccl_object<Ccl_rule>(record, key_memory_ccl);
|
|
if (!rule->bind_slot(level)) {
|
|
m_keyword_rules.push_back(rule);
|
|
num += 1;
|
|
} else {
|
|
destroy_object<Ccl_rule>(rule);
|
|
if (level == Ccl_error_level::CCL_CRITICAL) break;
|
|
}
|
|
}
|
|
m_keyword_size.store(m_keyword_rules.size());
|
|
DBUG_RETURN(num);
|
|
}
|
|
|
|
/**
|
|
Insert the command rules into cache.
|
|
report error message by level if failed.
|
|
|
|
@param[in] rules Rule set
|
|
@param[in] level Error level
|
|
|
|
@retval affected rule count
|
|
*/
|
|
size_t Ccl_rule_set::insert_command_rules(std::vector<Ccl_record *> &rules,
|
|
Ccl_error_level level) {
|
|
size_t num = 0;
|
|
Ccl_rule *rule = nullptr;
|
|
DBUG_ENTER("Ccl_rule_set::insert_command_rules");
|
|
for (auto it = rules.cbegin(); it != rules.cend(); ++it) {
|
|
Ccl_record *record = *it;
|
|
if (m_command_rule != nullptr) {
|
|
if (level == Ccl_error_level::CCL_CRITICAL) {
|
|
my_error(ER_CCL_DUPLICATE_RULE, MYF(0), record->id,
|
|
"load command rule");
|
|
break;
|
|
} else {
|
|
push_warning_printf(current_thd, Sql_condition::SL_WARNING,
|
|
ER_CCL_DUPLICATE_RULE,
|
|
ER_THD(current_thd, ER_CCL_DUPLICATE_RULE),
|
|
record->id, "load command rule");
|
|
continue;
|
|
}
|
|
}
|
|
rule = allocate_ccl_object<Ccl_rule>(record, key_memory_ccl);
|
|
if ((!rule->bind_slot(level))) {
|
|
m_command_rule = rule;
|
|
num += 1;
|
|
} else {
|
|
destroy_object<Ccl_rule>(rule);
|
|
if (level == Ccl_error_level::CCL_CRITICAL) break;
|
|
}
|
|
}
|
|
m_command_size.store(m_command_rule == nullptr ? 0 : 1);
|
|
DBUG_RETURN(num);
|
|
}
|
|
|
|
Ccl_slot *Ccl_rule::retrieve_slot(ulonglong *version) {
|
|
*version = m_slot->get_version();
|
|
m_matched++;
|
|
return m_slot;
|
|
}
|
|
|
|
static Ccl_show_result *get_ccl_result(MEM_ROOT *mem_root,
|
|
const Ccl_rule *rule) {
|
|
Ccl_show_result *result = new (mem_root) Ccl_show_result();
|
|
result->id = rule->get_rule_id();
|
|
const char *type = ccl_rule_type_str[static_cast<size_t>(rule->get_type())];
|
|
result->type.str = type;
|
|
result->type.length = strlen(type);
|
|
|
|
result->schema.str = strmake_root(mem_root, rule->get_schema().c_str(),
|
|
rule->get_schema().length());
|
|
result->schema.length = rule->get_schema().length();
|
|
|
|
result->table.str = strmake_root(mem_root, rule->get_table().c_str(),
|
|
rule->get_table().length());
|
|
result->table.length = rule->get_table().length();
|
|
|
|
char state = ccl_rule_state_str[static_cast<size_t>(rule->get_state())];
|
|
result->state.str = strmake_root(mem_root, &state, 1);
|
|
result->state.length = 1;
|
|
|
|
char ordered = ccl_rule_ordered_str[static_cast<size_t>(rule->get_ordered())];
|
|
result->ordered.str = strmake_root(mem_root, &ordered, 1);
|
|
result->ordered.length = 1;
|
|
|
|
result->concurrency_count = rule->get_concurrency_count();
|
|
result->matched = rule->get_matched();
|
|
result->running = rule->get_slot()->get_running();
|
|
result->waiting = rule->get_slot()->get_waiting();
|
|
|
|
result->keywords.str = strmake_root(mem_root, rule->get_keywords().c_str(),
|
|
rule->get_keywords().length());
|
|
result->keywords.length = rule->get_keywords().length();
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Collect all active queue buckets.
|
|
|
|
@param[in] mem_root memory pool
|
|
@param[out] results rule result container
|
|
*/
|
|
void Ccl_rule_set::collect_queue_buckets(
|
|
MEM_ROOT *mem_root, std::vector<Ccl_show_result *> &results) {
|
|
Ccl_show_result *result;
|
|
|
|
Rule_lock_helper queue_lock(&m_queue_lock, false);
|
|
DBUG_ASSERT(queue_lock.effect());
|
|
for (Queue_buckets::const_iterator it = m_queue_buckets.cbegin();
|
|
it != m_queue_buckets.cend(); it++) {
|
|
if ((result = get_ccl_result(mem_root, *it))) results.push_back(result);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Collect all active rules.
|
|
|
|
@param[in] mem_root memory pool
|
|
@param[out] results rule result container
|
|
*/
|
|
void Ccl_rule_set::collect_rules(MEM_ROOT *mem_root,
|
|
std::vector<Ccl_show_result *> &results) {
|
|
Ccl_show_result *result;
|
|
Rule_lock_helper intact_lock(&m_intact_lock, false);
|
|
DBUG_ASSERT(intact_lock.effect());
|
|
for (Intact_object_rules::const_iterator it = m_intact_rules.cbegin();
|
|
it != m_intact_rules.cend(); it++) {
|
|
if ((result = get_ccl_result(mem_root, it->second)))
|
|
results.push_back(result);
|
|
}
|
|
intact_lock.unlock();
|
|
|
|
Rule_lock_helper keyword_lock(&m_keyword_lock, false);
|
|
DBUG_ASSERT(keyword_lock.effect());
|
|
for (Keyword_rules::const_iterator it = m_keyword_rules.cbegin();
|
|
it != m_keyword_rules.cend(); it++) {
|
|
if ((result = get_ccl_result(mem_root, *it))) results.push_back(result);
|
|
}
|
|
keyword_lock.unlock();
|
|
|
|
Rule_lock_helper command_lock(&m_command_lock, false);
|
|
DBUG_ASSERT(command_lock.effect());
|
|
if (m_command_rule) {
|
|
if ((result = get_ccl_result(mem_root, m_command_rule)))
|
|
results.push_back(result);
|
|
}
|
|
command_lock.unlock();
|
|
}
|
|
/**
|
|
Comply the queue rules.
|
|
|
|
@param[in] hash hash value
|
|
@param[out] version the matched slot version
|
|
|
|
@retval slot
|
|
*/
|
|
Ccl_slot *Ccl_rule_set::comply_queue(ulonglong hash, ulonglong *version) {
|
|
DBUG_ENTER("Ccl_rule_set::comply_queue");
|
|
if (m_queue_size.load() > 0) {
|
|
Rule_lock_helper queue_lock(&m_queue_lock, false);
|
|
DBUG_ASSERT(queue_lock.effect());
|
|
DBUG_ASSERT(m_queue_size.load() == m_queue_buckets.size());
|
|
Ccl_rule *rule = m_queue_buckets[hash % m_queue_size.load()];
|
|
if (rule) DBUG_RETURN(rule->retrieve_slot(version));
|
|
}
|
|
DBUG_RETURN(nullptr);
|
|
}
|
|
/**
|
|
Comply the rules, the matching order is : intact rule, keyword rule,
|
|
command rule.
|
|
|
|
@param[in] all_tables Table list
|
|
@param[in] query Statement string
|
|
@param[out] version the matched slot version
|
|
|
|
@retval slot
|
|
*/
|
|
Ccl_slot *Ccl_rule_set::comply_rule(TABLE_LIST *all_tables,
|
|
const LEX_CSTRING &query,
|
|
ulonglong *version) {
|
|
/* 1. check the intact rules */
|
|
if (all_tables && m_intact_size.load() > 0) {
|
|
Rule_lock_helper intact_lock(&m_intact_lock, false);
|
|
DBUG_ASSERT(intact_lock.effect());
|
|
TABLE_LIST *table;
|
|
for (table = all_tables; table; table = table->next_global) {
|
|
typename Intact_object_rules::const_iterator it;
|
|
it = m_intact_rules.find(std::pair<String_ccl, String_ccl>(
|
|
String_ccl(table->db), String_ccl(table->table_name)));
|
|
|
|
if (it == m_intact_rules.end())
|
|
continue;
|
|
else {
|
|
Ccl_rule *rule = const_cast<Ccl_rule *>(it->second);
|
|
if (rule->keyword_is_null() || rule->match_keyword(query)) {
|
|
return rule->retrieve_slot(version);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 2. check the keyword rules. */
|
|
|
|
if (m_keyword_size.load() > 0) {
|
|
Rule_lock_helper keyword_lock(&m_keyword_lock, false);
|
|
DBUG_ASSERT(keyword_lock.effect());
|
|
|
|
for (Keyword_rules::const_iterator it = m_keyword_rules.cbegin();
|
|
it != m_keyword_rules.cend(); it++) {
|
|
Ccl_rule *rule = *it;
|
|
|
|
if (!rule->keyword_is_null() && rule->match_keyword(query))
|
|
return rule->retrieve_slot(version);
|
|
}
|
|
}
|
|
|
|
/* 3. check the command rules. */
|
|
|
|
if (m_command_size.load() == 1) {
|
|
Rule_lock_helper command_lock(&m_command_lock, false);
|
|
DBUG_ASSERT(command_lock.effect());
|
|
if (m_command_rule) return m_command_rule->retrieve_slot(version);
|
|
}
|
|
|
|
/* not match */
|
|
return nullptr;
|
|
}
|
|
/**
|
|
Collect all active rules.
|
|
|
|
@param[in] mem_root memory pool
|
|
@param[out] results rule result container
|
|
*/
|
|
void System_ccl::collect_rules(MEM_ROOT *mem_root,
|
|
std::vector<Ccl_show_result *> &results) {
|
|
m_select_rule_set.collect_rules(mem_root, results);
|
|
m_update_rule_set.collect_rules(mem_root, results);
|
|
m_insert_rule_set.collect_rules(mem_root, results);
|
|
m_delete_rule_set.collect_rules(mem_root, results);
|
|
}
|
|
|
|
/**
|
|
Collect all active queue buckets.
|
|
|
|
@param[in] mem_root memory pool
|
|
@param[out] results rule result container
|
|
*/
|
|
void System_ccl::collect_queue_buckets(
|
|
MEM_ROOT *mem_root, std::vector<Ccl_show_result *> &results) {
|
|
m_queue_bucket_set.collect_queue_buckets(mem_root, results);
|
|
}
|
|
|
|
/* Clear all rules */
|
|
void System_ccl::clear_rules() {
|
|
m_select_rule_set.clear_rules();
|
|
m_update_rule_set.clear_rules();
|
|
m_insert_rule_set.clear_rules();
|
|
m_delete_rule_set.clear_rules();
|
|
}
|
|
/**
|
|
Refresh the rules.
|
|
Report the error message by level.
|
|
|
|
@param[in] group rule group
|
|
@param[in] type rule type
|
|
@param[in] force_clearn whether clear cache
|
|
@param[in] level error level
|
|
|
|
@retval added rule count
|
|
*/
|
|
size_t System_ccl::refresh_rules(Ccl_record_group &group, Ccl_rule_type type,
|
|
bool force_clean, Ccl_error_level level) {
|
|
DBUG_ENTER("System_ccl::refresh_rules");
|
|
DBUG_RETURN(get_rule_set(type)->refresh_rules(group, force_clean, level));
|
|
}
|
|
|
|
/**
|
|
Delete the rule by rule id.
|
|
|
|
@param[in] rule_id rule id
|
|
|
|
@retval affected rule count
|
|
*/
|
|
size_t System_ccl::delete_rule(ulonglong rule_id) {
|
|
DBUG_ENTER("System_ccl::delete_rule");
|
|
DBUG_RETURN(m_select_rule_set.delete_rule(rule_id) +
|
|
m_update_rule_set.delete_rule(rule_id) +
|
|
m_insert_rule_set.delete_rule(rule_id) +
|
|
m_delete_rule_set.delete_rule(rule_id));
|
|
}
|
|
/**
|
|
Constructor of Ccl_slot.
|
|
*/
|
|
Ccl_slot::Ccl_slot() {
|
|
mysql_mutex_init(key_LOCK_ccl_slot, &m_mutex, MY_MUTEX_INIT_FAST);
|
|
mysql_cond_init(key_COND_ccl_slot, &m_cond);
|
|
|
|
m_concurrency_count = 0;
|
|
m_running = 0;
|
|
m_waiting = 0;
|
|
m_version = 0;
|
|
m_state.store(false);
|
|
}
|
|
|
|
/**
|
|
Destructor of Ccl_slot.
|
|
*/
|
|
Ccl_slot::~Ccl_slot() {
|
|
DBUG_ENTER("Ccl_slot::~Ccl_slot");
|
|
DBUG_ASSERT(m_running == 0 && m_waiting == 0);
|
|
DBUG_ASSERT(m_state.load() == false);
|
|
mysql_mutex_destroy(&m_mutex);
|
|
mysql_cond_destroy(&m_cond);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
void Ccl_slot::lock() { mysql_mutex_lock(&m_mutex); }
|
|
void Ccl_slot::unlock() { mysql_mutex_unlock(&m_mutex); }
|
|
/**
|
|
Acquire available slot;
|
|
|
|
@param[in] version Unique version
|
|
@param[in] cc_count Concurrency count
|
|
|
|
@retval true Success
|
|
@retval false Failure
|
|
*/
|
|
bool Ccl_slot::acquire(ulonglong version, ulonglong cc_count) {
|
|
bool old_value = false;
|
|
bool new_value = true;
|
|
bool success;
|
|
DBUG_ENTER("Ccl_slot::acquire");
|
|
if ((success =
|
|
atomic_compare_exchange_strong(&m_state, &old_value, new_value))) {
|
|
lock();
|
|
m_version = version;
|
|
set_concurrency(cc_count);
|
|
/**
|
|
Maybe some waiters still wait last rule that has been destroyed,
|
|
and this slot is being assigned new rule.
|
|
*/
|
|
mysql_cond_broadcast(&m_cond);
|
|
unlock();
|
|
}
|
|
DBUG_RETURN(success);
|
|
}
|
|
|
|
/**
|
|
Enter the condition.
|
|
|
|
@param[in] version the thread local version.
|
|
*/
|
|
void Ccl_slot::enter_cond(THD *thd, ulonglong version) {
|
|
int wait_result = 0;
|
|
struct timespec abs_timeout;
|
|
ulonglong timeout_cnt = 0;
|
|
DBUG_ENTER("Ccl_slot::enter_cond");
|
|
ulonglong max_times = ccl_wait_timeout / CCL_RETRY_INTERVAL;
|
|
const char *save_proc_info = thd->proc_info;
|
|
Global_THD_manager *thd_manager = Global_THD_manager::get_instance();
|
|
|
|
thd_manager->dec_thread_running();
|
|
thd_proc_info(thd, "Concurrency control waiting");
|
|
lock();
|
|
|
|
m_running++;
|
|
/* Listen to the wakeup by other thread */
|
|
thd_enter_cond(thd, &m_cond, &m_mutex, NULL, NULL, __func__, __FILE__,
|
|
__LINE__);
|
|
thd_wait_begin(thd, THD_WAIT_SLEEP);
|
|
|
|
if(m_concurrency_count == 0) {
|
|
my_error(ER_CCL_REFUSE_QUERY, MYF(0));
|
|
goto end;
|
|
}
|
|
|
|
if(ccl_max_waiting_count != 0 && m_waiting >= ccl_max_waiting_count) {
|
|
my_error(ER_CCL_MAX_WAITING_COUNT, MYF(0));
|
|
goto end;
|
|
}
|
|
/**
|
|
Wait requirement:
|
|
1) retry times is not more than ccl_wait_timeout.
|
|
2) slot is active.
|
|
3) version is equal thread local version. it demostrate rule is not changed.
|
|
4) running thread is more than concurrency control limit.
|
|
5) thd is not killed.
|
|
*/
|
|
while (timeout_cnt < max_times && m_state.load() == true &&
|
|
version == m_version && m_running > m_concurrency_count &&
|
|
!thd->is_killed()) {
|
|
/* Decrease the running and increase the waiting */
|
|
m_running--;
|
|
m_waiting++;
|
|
set_timespec(&abs_timeout, 5);
|
|
wait_result = mysql_cond_timedwait(&m_cond, &m_mutex, &abs_timeout);
|
|
/* Increase the timeout times */
|
|
if (is_timeout(wait_result)) timeout_cnt++;
|
|
m_waiting--;
|
|
m_running++;
|
|
}
|
|
|
|
end:
|
|
thd_wait_end(thd);
|
|
|
|
unlock();
|
|
thd_exit_cond(thd, NULL, __func__, __FILE__, __LINE__);
|
|
|
|
thd_proc_info(thd, save_proc_info);
|
|
thd_manager->inc_thread_running();
|
|
|
|
DBUG_EXECUTE_IF("cond_wait_sleep_10", {
|
|
DBUG_SET("-d, cond_wait_sleep_10");
|
|
my_sleep(10 * 1000 * 1000);
|
|
});
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
/**
|
|
Exit the condition.
|
|
*/
|
|
void Ccl_slot::exit_cond() {
|
|
DBUG_ENTER("Ccl_slot::exit_cond");
|
|
lock();
|
|
m_running--;
|
|
mysql_cond_broadcast(&m_cond);
|
|
unlock();
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/**
|
|
Release the slot;
|
|
*/
|
|
void Ccl_slot::release() {
|
|
bool old_value = true;
|
|
bool new_value = false;
|
|
bool success;
|
|
DBUG_ENTER("Ccl_slot::release");
|
|
if ((success =
|
|
atomic_compare_exchange_strong(&m_state, &old_value, new_value))) {
|
|
lock();
|
|
reset_concurrency();
|
|
/**
|
|
Wakeup the waiters mainly release will reset
|
|
concurrency count condition
|
|
*/
|
|
mysql_cond_broadcast(&m_cond);
|
|
unlock();
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
} /* namespace im */
|