diff --git a/.github/workflows/CI-3p-django-framework.yml b/.github/workflows/CI-3p-django-framework.yml index c62a3f4c09..8c50f9cf5b 100644 --- a/.github/workflows/CI-3p-django-framework.yml +++ b/.github/workflows/CI-3p-django-framework.yml @@ -17,7 +17,8 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} - + cancel-in-progress: true + jobs: run: uses: sysown/proxysql/.github/workflows/ci-3p-django-framework.yml@GH-Actions diff --git a/.github/workflows/CI-3p-laravel-framework.yml b/.github/workflows/CI-3p-laravel-framework.yml index 48b6246d13..fb6f3a8c70 100644 --- a/.github/workflows/CI-3p-laravel-framework.yml +++ b/.github/workflows/CI-3p-laravel-framework.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-3p-mariadb-connector-c.yml b/.github/workflows/CI-3p-mariadb-connector-c.yml index ad8062e036..048f94f899 100644 --- a/.github/workflows/CI-3p-mariadb-connector-c.yml +++ b/.github/workflows/CI-3p-mariadb-connector-c.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-3p-mysql-connector-j.yml b/.github/workflows/CI-3p-mysql-connector-j.yml index 2389589849..d327a9f506 100644 --- a/.github/workflows/CI-3p-mysql-connector-j.yml +++ b/.github/workflows/CI-3p-mysql-connector-j.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-3p-php-pdo-mysql.yml b/.github/workflows/CI-3p-php-pdo-mysql.yml index a0f88244f9..207ff1bdb9 100644 --- a/.github/workflows/CI-3p-php-pdo-mysql.yml +++ b/.github/workflows/CI-3p-php-pdo-mysql.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-3p-sqlalchemy.yml b/.github/workflows/CI-3p-sqlalchemy.yml index 83a937fae0..335fb88bac 100644 --- a/.github/workflows/CI-3p-sqlalchemy.yml +++ b/.github/workflows/CI-3p-sqlalchemy.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-basictests.yml b/.github/workflows/CI-basictests.yml index 1195a0ac36..9ff6bc13e2 100644 --- a/.github/workflows/CI-basictests.yml +++ b/.github/workflows/CI-basictests.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-builds.yml b/.github/workflows/CI-builds.yml index 0afd290b79..020606da9f 100644 --- a/.github/workflows/CI-builds.yml +++ b/.github/workflows/CI-builds.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-codeql.yml b/.github/workflows/CI-codeql.yml index 1d71497b44..b283cb7bee 100644 --- a/.github/workflows/CI-codeql.yml +++ b/.github/workflows/CI-codeql.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-maketest.yml b/.github/workflows/CI-maketest.yml index 8c2b25a97b..5194374f8a 100644 --- a/.github/workflows/CI-maketest.yml +++ b/.github/workflows/CI-maketest.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-package-build.yml b/.github/workflows/CI-package-build.yml index 5ee9b902d8..2b68ee483c 100644 --- a/.github/workflows/CI-package-build.yml +++ b/.github/workflows/CI-package-build.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-repltests.yml b/.github/workflows/CI-repltests.yml index ea067e70fc..c4d2adbb05 100644 --- a/.github/workflows/CI-repltests.yml +++ b/.github/workflows/CI-repltests.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-selftests.yml b/.github/workflows/CI-selftests.yml index 1e9bab45e0..93f96712c7 100644 --- a/.github/workflows/CI-selftests.yml +++ b/.github/workflows/CI-selftests.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-shuntest.yml b/.github/workflows/CI-shuntest.yml index 406ac6af30..724eec8467 100644 --- a/.github/workflows/CI-shuntest.yml +++ b/.github/workflows/CI-shuntest.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-taptests-asan.yml b/.github/workflows/CI-taptests-asan.yml index b17d881eae..fd819701ae 100644 --- a/.github/workflows/CI-taptests-asan.yml +++ b/.github/workflows/CI-taptests-asan.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-taptests-groups.yml b/.github/workflows/CI-taptests-groups.yml index d5d2d2459e..b3cc108e8f 100644 --- a/.github/workflows/CI-taptests-groups.yml +++ b/.github/workflows/CI-taptests-groups.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-taptests-ssl.yml b/.github/workflows/CI-taptests-ssl.yml index c1dfc1a333..0c903a72d1 100644 --- a/.github/workflows/CI-taptests-ssl.yml +++ b/.github/workflows/CI-taptests-ssl.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/.github/workflows/CI-taptests.yml b/.github/workflows/CI-taptests.yml index 5749c3fc8a..b9531eb825 100644 --- a/.github/workflows/CI-taptests.yml +++ b/.github/workflows/CI-taptests.yml @@ -17,6 +17,7 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true jobs: run: diff --git a/Makefile b/Makefile index 6508bd5ce6..724cd64bcd 100644 --- a/Makefile +++ b/Makefile @@ -344,9 +344,9 @@ build-%: .NOTPARALLEL: binaries/proxysql% binaries/proxysql%: - @docker-compose -p proxysql down -v --remove-orphans - @docker-compose -p proxysql up $(IMG_NAME)$(IMG_TYPE)$(IMG_COMP)_build - @docker-compose -p proxysql down -v --remove-orphans + @docker compose -p proxysql down -v --remove-orphans + @docker compose -p proxysql up $(IMG_NAME)$(IMG_TYPE)$(IMG_COMP)_build + @docker compose -p proxysql down -v --remove-orphans ### clean targets diff --git a/deps/json/json_fwd.hpp b/deps/json/json_fwd.hpp new file mode 100644 index 0000000000..83c21f857b --- /dev/null +++ b/deps/json/json_fwd.hpp @@ -0,0 +1,175 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + +#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ +#define INCLUDE_NLOHMANN_JSON_FWD_HPP_ + +#include // int64_t, uint64_t +#include // map +#include // allocator +#include // string +#include // vector + +// #include +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.2 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2022 Niels Lohmann +// SPDX-License-Identifier: MIT + + + +// This file contains all macro definitions affecting or depending on the ABI + +#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK + #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) + #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 + #warning "Already included a different version of the library!" + #endif + #endif +#endif + +#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) +#define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) + +#ifndef JSON_DIAGNOSTICS + #define JSON_DIAGNOSTICS 0 +#endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_DIAGNOSTICS + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag +#else + #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp +#else + #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION + #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 +#endif + +// Construct the namespace ABI tags component +#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b +#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ + NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) + +#define NLOHMANN_JSON_ABI_TAGS \ + NLOHMANN_JSON_ABI_TAGS_CONCAT( \ + NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ + NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) + +// Construct the namespace version component +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ + _v ## major ## _ ## minor ## _ ## patch +#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) + +#if NLOHMANN_JSON_NAMESPACE_NO_VERSION +#define NLOHMANN_JSON_NAMESPACE_VERSION +#else +#define NLOHMANN_JSON_NAMESPACE_VERSION \ + NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ + NLOHMANN_JSON_VERSION_MINOR, \ + NLOHMANN_JSON_VERSION_PATCH) +#endif + +// Combine namespace components +#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b +#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ + NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) + +#ifndef NLOHMANN_JSON_NAMESPACE +#define NLOHMANN_JSON_NAMESPACE \ + nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN +#define NLOHMANN_JSON_NAMESPACE_BEGIN \ + namespace nlohmann \ + { \ + inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ + NLOHMANN_JSON_ABI_TAGS, \ + NLOHMANN_JSON_NAMESPACE_VERSION) \ + { +#endif + +#ifndef NLOHMANN_JSON_NAMESPACE_END +#define NLOHMANN_JSON_NAMESPACE_END \ + } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ + } // namespace nlohmann +#endif + + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +NLOHMANN_JSON_NAMESPACE_BEGIN + +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer; + +/// a class to store JSON values +/// @sa https://json.nlohmann.me/api/basic_json/ +template class ObjectType = + std::map, + template class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = + adl_serializer, + class BinaryType = std::vector> +class basic_json; + +/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document +/// @sa https://json.nlohmann.me/api/json_pointer/ +template +class json_pointer; + +/*! +@brief default specialization +@sa https://json.nlohmann.me/api/json/ +*/ +using json = basic_json<>; + +/// @brief a minimal map-like container that preserves insertion order +/// @sa https://json.nlohmann.me/api/ordered_map/ +template +struct ordered_map; + +/// @brief specialization that maintains the insertion order of object keys +/// @sa https://json.nlohmann.me/api/ordered_json/ +using ordered_json = basic_json; + +NLOHMANN_JSON_NAMESPACE_END + +#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ diff --git a/doc/internal/MySQL_Connection.md b/doc/internal/MySQL_Connection.md new file mode 100644 index 0000000000..50377d3c23 --- /dev/null +++ b/doc/internal/MySQL_Connection.md @@ -0,0 +1,70 @@ +### Flowchart of `MySQL_Connection::async_query()` + +This function asynchronously executes a query on the MySQL connection. +It handles various states of the asynchronous query execution process and returns appropriate status codes indicating the result of the execution. + +Returns an integer status code indicating the result of the query execution: +- 0: Query execution completed successfully. +- -1: Query execution failed. +- 1: Query execution in progress. +- 2: Processing a multi-statement query, control needs to be transferred to MySQL_Session. +- 3: In the middle of processing a multi-statement query. + + +```mermaid +--- +title: MySQL_Connection::async_query() +--- +flowchart TD +Assert["assert()"] +ValidConnection{Valid Connection} +ValidConnection -- no --> Assert +IsServerOffline{"IsServerOffline()"} +ValidConnection -- yes --> IsServerOffline +IsServerOffline -- yes --> ReturnMinus1 +asyncStateMachine1{async_state_machine} +asyncStateMachine2{async_state_machine} +IsServerOffline -- no --> asyncStateMachine1 +asyncStateMachine1 -- ASYNC_QUERY_END --> Return0 +handler["handler()"] +asyncStateMachine1 --> handler +handler --> asyncStateMachine2 +asyncStateMachine2 -- ASYNC_QUERY_END --> mysql_error{"mysql_error"} +asyncStateMachine2 -- ASYNC_STMT_EXECUTE_END --> mysql_error +asyncStateMachine2 -- ASYNC_STMT_PREPARE_FAILED --> ReturnMinus1 +asyncStateMachine2 -- ASYNC_STMT_PREPARE_SUCCESSFUL --> Return0 +mysql_error -- yes --> ReturnMinus1 +mysql_error -- no --> Return0 +asyncStateMachine2 -- ASYNC_NEXT_RESULT_START --> Return2 +processing_multi_statement{"processing_multi_statement"} +asyncStateMachine2 --> processing_multi_statement +processing_multi_statement -- yes --> Return3 +processing_multi_statement -- no --> Return1 +ReturnMinus1["return -1"] +Return0["return 0"] +Return1["return 1"] +Return2["return 2"] +Return3["return 3"] +``` + +### Flowchart of `MySQL_Connection::IsServerOffline()` + +```mermaid +--- +title: MySQL_Connection::IsServerOffline() +--- +flowchart TD +True[true] +False[false] +SS1{"server_status"} +SA{"shunned_automatic"} +SB{"shunned_and_kill_all_connections"} +SS1 -- OFFLINE_HARD --> True +SS1 -- REPLICATION_LAG --> True +SS1 -- SHUNNED --> SA +SA -- yes --> SB +SB -- yes --> True +SA -- no --> False +SB -- no --> False +SS1 --> False +``` diff --git a/doc/internal/MySQL_Session.md b/doc/internal/MySQL_Session.md new file mode 100644 index 0000000000..e56fc6c293 --- /dev/null +++ b/doc/internal/MySQL_Session.md @@ -0,0 +1,68 @@ +### Flowchart of `MySQL_Session::RunQuery()` + +This function mostly calls `MySQL_Connection::async_query()` with the right arguments. +Returns an integer status code indicating the result of the query execution: +- 0: Query execution completed successfully. +- -1: Query execution failed. +- 1: Query execution in progress. +- 2: Processing a multi-statement query, control needs to be transferred to MySQL_Session. +- 3: In the middle of processing a multi-statement query. + +```mermaid +--- +title: MySQL_Session::RunQuery() +--- +flowchart TD +RQ["MySQL_Connection::async_query()"] +BEGIN --> RQ +RQ --> END +``` + +### Flowchart of `MySQL_Session::handler()` + +WORK IN PROGRESS + +```mermaid +--- +title: MySQL_Session::handler() +--- +flowchart TD +RQ["rc = RunQuery()"] +RC{rc} +CBCS["rc1 = handler_ProcessingQueryError_CheckBackendConnectionStatus()"] +RC1{rc1} +RQ --> RC +RC -- 0 --> OK +RC -- -1 --> CBCS +CBCS --> RC1 +CS["CONNECTING_SERVER"] +ReturnMinus1["return -1"] +RC1 -- -1 --> ReturnMinus1 +RC1 -- 1 --> CS +HM1CLE1["handler_minus1_ClientLibraryError()"] +HM1CLE2["handler_minus1_ClientLibraryError()"] +myerr1{"myerr >= 2000 +&& +myerr < 3000"} +RC1 --> myerr1 +myerr1 -- yes --> HM1CLE1 +HM1CLE1 -- true --> CS +HM1CLE1 -- false --> ReturnMinus1 +HM1LEDQ1["handler_minus1_LogErrorDuringQuery()"] +myerr1 -- no --> HM1LEDQ1 +HM1HEC1["handler_minus1_HandleErrorCodes()"] +HM1LEDQ1 --> HM1HEC1 +HM1HEC1 -- true --> HR1{"handler_ret"} +HR1 -- 0 --> CS +HR1 --> RHR1["return handler_ret"] +HM1GEM1["handler_minus1_GenerateErrorMessage()"] +HM1HEC1 -- false --> HM1GEM1 +RE["RequestEnd()"] +HM1HBC1["handler_minus1_HandleBackendConnection()"] +HM1GEM1 --> RE +RE --> HM1HBC1 +``` + + +### Flowchart of `MySQL_Session::handler_ProcessingQueryError_CheckBackendConnectionStatus()` +TODO diff --git a/include/GTID_Server_Data.h b/include/GTID_Server_Data.h new file mode 100644 index 0000000000..9bc9219fda --- /dev/null +++ b/include/GTID_Server_Data.h @@ -0,0 +1,27 @@ +#ifndef CLASS_GTID_Server_Data_H +#define CLASS_GTID_Server_Data_H +class GTID_Server_Data { + public: + char *address; + uint16_t port; + uint16_t mysql_port; + char *data; + size_t len; + size_t size; + size_t pos; + struct ev_io *w; + char uuid_server[64]; + unsigned long long events_read; + gtid_set_t gtid_executed; + bool active; + GTID_Server_Data(struct ev_io *_w, char *_address, uint16_t _port, uint16_t _mysql_port); + void resize(size_t _s); + ~GTID_Server_Data(); + bool readall(); + bool writeout(); + bool read_next_gtid(); + bool gtid_exists(char *gtid_uuid, uint64_t gtid_trxid); + void read_all_gtids(); + void dump(); +}; +#endif // CLASS_GTID_Server_Data_H diff --git a/include/MySQL_Data_Stream.h b/include/MySQL_Data_Stream.h index 489c9cf974..1ce5fed802 100644 --- a/include/MySQL_Data_Stream.h +++ b/include/MySQL_Data_Stream.h @@ -272,5 +272,7 @@ class MySQL_Data_Stream void destroy_queues(); bool data_in_rbio(); + + void get_client_myds_info_json(json&); }; #endif /* __CLASS_MYSQL_DATA_STREAM_H */ diff --git a/include/MySQL_HostGroups_Manager.h b/include/MySQL_HostGroups_Manager.h index f01a030ee2..fa86935907 100644 --- a/include/MySQL_HostGroups_Manager.h +++ b/include/MySQL_HostGroups_Manager.h @@ -130,6 +130,9 @@ class MyHGC; std::string gtid_executed_to_string(gtid_set_t& gtid_executed); void addGtid(const gtid_t& gtid, gtid_set_t& gtid_executed); +#include "GTID_Server_Data.h" + +/* class GTID_Server_Data { public: char *address; @@ -154,7 +157,7 @@ class GTID_Server_Data { void read_all_gtids(); void dump(); }; - +*/ class MySrvConnList { @@ -198,7 +201,6 @@ class MySrvC { // MySQL Server Container uint16_t gtid_port; uint16_t flags; int64_t weight; - enum MySerStatus status; unsigned int compression; int64_t max_connections; unsigned int aws_aurora_current_lag_us; @@ -260,6 +262,11 @@ class MySrvC { // MySQL Server Container max_connections_used = connections_used; return max_connections_used; } + void set_status(MySerStatus _status); + inline + MySerStatus get_status() const { return status; } +private: + enum MySerStatus status; }; class MySrvList { // MySQL Server List @@ -279,6 +286,8 @@ class MySrvList { // MySQL Server List class MyHGC { // MySQL Host Group Container public: unsigned int hid; + std::atomic num_online_servers; + time_t last_log_time_num_online_servers; unsigned long long current_time_now; uint32_t new_connections_now; MySrvList *mysrvs; @@ -310,6 +319,13 @@ class MyHGC { // MySQL Host Group Container MyHGC(int); ~MyHGC(); MySrvC *get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess); + void refresh_online_server_count(); + void log_num_online_server_count_error(); + inline + bool online_servers_within_threshold() const { + if (num_online_servers.load(std::memory_order_relaxed) <= attributes.max_num_online_servers) return true; + return false; + } }; class Group_Replication_Info { @@ -927,6 +943,9 @@ class MySQL_HostGroups_Manager { void init(); void wrlock(); void wrunlock(); +#ifdef DEBUG + bool is_locked = false; +#endif int servers_add(SQLite3_result *resultset); /** * @brief Generates a new global checksum for module 'mysql_servers_v2' using the provided hash. diff --git a/include/MySQL_Prepared_Stmt_info.h b/include/MySQL_Prepared_Stmt_info.h new file mode 100644 index 0000000000..ad221720a5 --- /dev/null +++ b/include/MySQL_Prepared_Stmt_info.h @@ -0,0 +1,25 @@ +#ifndef CLASS_MySQL_Prepared_Stmt_info_H +#define CLASS_MySQL_Prepared_Stmt_info_H +class MySQL_Prepared_Stmt_info { + public: + uint32_t statement_id; + uint16_t num_columns; + uint16_t num_params; + uint16_t warning_count; + uint16_t pending_num_columns; + uint16_t pending_num_params; + MySQL_Prepared_Stmt_info(unsigned char *pkt, unsigned int length) { + pkt += 5; + statement_id = CPY4(pkt); + pkt += sizeof(uint32_t); + num_columns = CPY2(pkt); + pkt += sizeof(uint16_t); + num_params = CPY2(pkt); + pkt += sizeof(uint16_t); + pkt++; // reserved_1 + warning_count = CPY2(pkt); + pending_num_columns=num_columns; + pending_num_params=num_params; + } +}; +#endif // CLASS_MySQL_Prepared_Stmt_info_H diff --git a/include/MySQL_Protocol.h b/include/MySQL_Protocol.h index 0d30b223bf..a95251f8bf 100644 --- a/include/MySQL_Protocol.h +++ b/include/MySQL_Protocol.h @@ -4,6 +4,7 @@ #include "proxysql.h" #include "cpp.h" #include "MySQL_Variables.h" +#include "MySQL_Prepared_Stmt_info.h" #define RESULTSET_BUFLEN 16300 @@ -70,16 +71,6 @@ class MySQL_ResultSet { unsigned long long current_size(); }; -class MySQL_Prepared_Stmt_info { - public: - uint32_t statement_id; - uint16_t num_columns; - uint16_t num_params; - uint16_t warning_count; - uint16_t pending_num_columns; - uint16_t pending_num_params; - MySQL_Prepared_Stmt_info(unsigned char *, unsigned int); -}; uint8_t mysql_decode_length(unsigned char *ptr, uint64_t *len); diff --git a/include/MySQL_Session.h b/include/MySQL_Session.h index d7b434e09c..2a593d3ce5 100644 --- a/include/MySQL_Session.h +++ b/include/MySQL_Session.h @@ -122,6 +122,7 @@ class MySQL_Session private: //int handler_ret; void handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t *, bool *); + void handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE_WrongCredentials(PtrSize_t *, bool *); // void handler___status_CHANGING_USER_CLIENT___STATE_CLIENT_HANDSHAKE(PtrSize_t *, bool *); @@ -202,7 +203,9 @@ class MySQL_Session void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___create_mirror_session(); int handler_again___status_PINGING_SERVER(); int handler_again___status_RESETTING_CONNECTION(); + bool handler_again___status_SHOW_WARNINGS(MySQL_Data_Stream *, bool); void handler_again___new_thread_to_kill_connection(); + void handler_KillConnectionIfNeeded(); bool handler_again___verify_init_connect(); bool handler_again___verify_ldap_user_variable(); @@ -210,6 +213,7 @@ class MySQL_Session bool handler_again___verify_backend_session_track_gtids(); bool handler_again___verify_backend_multi_statement(); bool handler_again___verify_backend_user_schema(); + bool handler_again___verify_multiple_variables(MySQL_Connection *); bool handler_again___status_SETTING_INIT_CONNECT(int *); bool handler_again___status_SETTING_LDAP_USER_VARIABLE(int *); bool handler_again___status_SETTING_SQL_MODE(int *); @@ -221,6 +225,7 @@ class MySQL_Session bool handler_again___status_CHANGING_AUTOCOMMIT(int *); bool handler_again___status_SETTING_MULTI_STMT(int *_rc); bool handler_again___multiple_statuses(int *rc); + void init(); void reset(); void add_ldap_comment_to_pkt(PtrSize_t *); @@ -229,7 +234,17 @@ class MySQL_Session * performing any processing on received client packets. */ void housekeeping_before_pkts(); + int get_pkts_from_client(bool&, PtrSize_t&); + + // GPFC_ functions are subfunctions of get_pkts_from_client() + int GPFC_Statuses2(bool&, PtrSize_t&); + void GPFC_DetectedMultiPacket_SetDDS(); + int GPFC_WaitingClientData_FastForwardSession(PtrSize_t&); + void GPFC_PreparedStatements(PtrSize_t&, unsigned char); + void GPFC_Replication_SwitchToFastForward(PtrSize_t&, unsigned char); + bool GPFC_QueryUSE(PtrSize_t&, int&); + void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_RESET(PtrSize_t&); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_CLOSE(PtrSize_t&); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_SEND_LONG_DATA(PtrSize_t&); @@ -249,6 +264,7 @@ class MySQL_Session int RunQuery(MySQL_Data_Stream *myds, MySQL_Connection *myconn); void handler___status_WAITING_CLIENT_DATA(); void handler_rc0_Process_GTID(MySQL_Connection *myconn); + void handler_rc0_RefreshActiveTransactions(MySQL_Connection* myconn); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB_replace_CLICKHOUSE(PtrSize_t& pkt); void handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___not_mysql(PtrSize_t& pkt); bool handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_detect_SQLi(); diff --git a/include/MySQL_Thread.h b/include/MySQL_Thread.h index 906b4ab433..dc36a0f9f6 100644 --- a/include/MySQL_Thread.h +++ b/include/MySQL_Thread.h @@ -129,6 +129,7 @@ class __attribute__((aligned(64))) MySQL_Thread void idle_thread_prepares_session_to_send_to_worker_thread(int i); void idle_thread_to_kill_idle_sessions(); bool move_session_to_idle_mysql_sessions(MySQL_Data_Stream *myds, unsigned int n); + void run_Handle_epoll_wait(int); #endif // IDLE_THREADS unsigned int find_session_idx_in_mysql_sessions(MySQL_Session *sess); @@ -142,6 +143,13 @@ class __attribute__((aligned(64))) MySQL_Thread void tune_timeout_for_session_needs_pause(MySQL_Data_Stream *myds); void configure_pollout(MySQL_Data_Stream *myds, unsigned int n); + void run_MoveSessionsBetweenThreads(); + void run_BootstrapListener(); + int run_ComputePollTimeout(); + void run_StopListener(); + void run_SetAllSession_ToProcess0(); + + protected: int nfds; @@ -212,6 +220,7 @@ class __attribute__((aligned(64))) MySQL_Thread void ProcessAllSessions_SortingSessions(); void ProcessAllSessions_CompletedMirrorSession(unsigned int& n, MySQL_Session *sess); void ProcessAllSessions_MaintenanceLoop(MySQL_Session *sess, unsigned long long sess_time, unsigned int& total_active_transactions_); + void ProcessAllSessions_Healthy0(MySQL_Session *sess, unsigned int& n); void process_all_sessions(); void refresh_variables(); void register_session_connection_handler(MySQL_Session *_sess, bool _new=false); diff --git a/include/MySQL_encode.h b/include/MySQL_encode.h new file mode 100644 index 0000000000..27273d6127 --- /dev/null +++ b/include/MySQL_encode.h @@ -0,0 +1,22 @@ +#ifndef CLASS_MySQL_encode_H +#define CLASS_MySQL_encode_H +#ifdef DEBUG +void __dump_pkt(const char *func, unsigned char *_ptr, unsigned int len); +#endif // DEBUG +char *sha1_pass_hex(char *sha1_pass); +double proxy_my_rnd(struct rand_struct *rand_st); +void proxy_create_random_string(char *_to, uint length, struct rand_struct *rand_st); +int write_encoded_length(unsigned char *p, uint64_t val, uint8_t len, char prefix); +int write_encoded_length_and_string(unsigned char *p, uint64_t val, uint8_t len, char prefix, char *string); +void proxy_compute_sha1_hash_multi(uint8_t *digest, const char *buf1, int len1, const char *buf2, int len2); +void proxy_compute_sha1_hash(uint8_t *digest, const char *buf, int len); +void proxy_compute_two_stage_sha1_hash(const char *password, size_t pass_len, uint8_t *hash_stage1, uint8_t *hash_stage2); +void proxy_my_crypt(char *to, const uint8_t *s1, const uint8_t *s2, uint len); +unsigned char decode_char(char x); +void unhex_pass(uint8_t *out, const char *in); +void proxy_scramble(char *to, const char *message, const char *password); +bool proxy_scramble_sha1(char *pass_reply, const char *message, const char *sha1_sha1_pass, char *sha1_pass); +unsigned int CPY3(unsigned char *ptr); +uint64_t CPY8(unsigned char *ptr); +uint8_t mysql_encode_length(uint64_t len, char *hd); +#endif // CLASS_MySQL_encode_H diff --git a/include/ProxySQL_Cluster.hpp b/include/ProxySQL_Cluster.hpp index eab5292cce..92d81efdec 100644 --- a/include/ProxySQL_Cluster.hpp +++ b/include/ProxySQL_Cluster.hpp @@ -378,6 +378,11 @@ struct fetch_query { std::string msgs[3]; }; +struct cluster_creds_t { + string user; + string pass; +}; + class ProxySQL_Cluster { private: SQLite3DB* mydb; @@ -444,7 +449,7 @@ class ProxySQL_Cluster { MySQL_Monitor::trigger_dns_cache_update(); } - void get_credentials(char**, char**); + cluster_creds_t get_credentials(); void set_username(char*); void set_password(char*); void set_admin_mysql_ifaces(char*); diff --git a/include/QP_rule_text.h b/include/QP_rule_text.h new file mode 100644 index 0000000000..9cbd7e7368 --- /dev/null +++ b/include/QP_rule_text.h @@ -0,0 +1,22 @@ +#ifndef CLASS_QR_RULE_H +#define CLASS_QR_RULE_H + +#define QP_RE_MOD_CASELESS 1 +#define QP_RE_MOD_GLOBAL 2 + +class QP_rule_text_hitsonly { + public: + char **pta; + QP_rule_text_hitsonly(QP_rule_t *QPr); + ~QP_rule_text_hitsonly(); +}; + +class QP_rule_text { + public: + char **pta; + int num_fields; + QP_rule_text(QP_rule_t *QPr); + ~QP_rule_text(); +}; + +#endif // CLASS_QR_RULE_H diff --git a/include/c_tokenizer.h b/include/c_tokenizer.h index abe0fdb30d..4444ca2e6a 100644 --- a/include/c_tokenizer.h +++ b/include/c_tokenizer.h @@ -1,5 +1,3 @@ -/* c_tokenizer.h */ -// some code borrowed from http://www.cplusplus.com/faq/sequences/strings/split/ #pragma once #ifndef C_TOKENIZER_H @@ -34,7 +32,6 @@ const char* free_tokenizer( tokenizer_t* tokenizer ); const char* tokenize( tokenizer_t* tokenizer ); char * mysql_query_digest_first_stage(const char* const q, int q_len, char** const fst_cmnt, char* const buf); char * mysql_query_digest_second_stage(const char* const q, int q_len, char** const fst_cmnt, char* const buf); -char * mysql_query_digest_and_first_comment(char *s , int len , char **first_comment, char *buf); char * mysql_query_digest_and_first_comment_2(const char* const q, int q_len, char** const fst_cmnt, char* const buf); char * mysql_query_digest_and_first_comment_one_it(char *s , int len , char **first_comment, char *buf); char * mysql_query_strip_comments(char *s , int len); diff --git a/include/mysql_connection.h b/include/mysql_connection.h index 350a8940ca..f88e41eb35 100644 --- a/include/mysql_connection.h +++ b/include/mysql_connection.h @@ -26,7 +26,7 @@ class MySQLServers_SslParams; class Variable { public: char *value = (char*)""; - void fill_server_internal_session(json &j, int conn_num, int idx); + void fill_server_internal_session(json &j, int idx); void fill_client_internal_session(json &j, int idx); }; @@ -60,6 +60,15 @@ class MySQL_Connection { void update_warning_count_from_statement(); bool is_expired(unsigned long long timeout); unsigned long long inserted_into_pool; + void connect_start_SetAttributes(); + void connect_start_SetCharset(); + void connect_start_SetClientFlag(unsigned long&); + char * connect_start_DNS_lookup(); + void connect_start_SetSslSettings(); + void ProcessQueryAndSetStatusFlags_Warnings(char *); + void ProcessQueryAndSetStatusFlags_UserVariables(char *, int); + void ProcessQueryAndSetStatusFlags_Savepoint(char *); + void ProcessQueryAndSetStatusFlags_SetBackslashEscapes(); public: struct { char *server_version; @@ -254,5 +263,8 @@ class MySQL_Connection { unsigned int number_of_matching_session_variables(const MySQL_Connection *client_conn, unsigned int& not_matching); unsigned long get_mysql_thread_id() { return mysql ? mysql->thread_id : 0; } static void set_ssl_params(MYSQL *mysql, MySQLServers_SslParams *ssl_params); + + void get_mysql_info_json(json&); + void get_backend_conn_info_json(json&); }; #endif /* __CLASS_MYSQL_CONNECTION_H */ diff --git a/include/proxysql_admin.h b/include/proxysql_admin.h index 9605b1652e..81f7aae6ea 100644 --- a/include/proxysql_admin.h +++ b/include/proxysql_admin.h @@ -15,6 +15,8 @@ #include "ProxySQL_RESTAPI_Server.hpp" +#include "proxysql_typedefs.h" + typedef struct { uint32_t hash; uint32_t key; } t_symstruct; class ProxySQL_Config; class ProxySQL_Restapi; @@ -338,6 +340,18 @@ class ProxySQL_Admin { void flush_mysql_variables___runtime_to_database(SQLite3DB *db, bool replace, bool del, bool onlyifempty, bool runtime=false, bool use_lock=true); void flush_mysql_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum = "", const time_t epoch = 0); + void flush_GENERIC_variables__checksum__database_to_runtime(const std::string& modname, const std::string& checksum, const time_t epoch); + bool flush_GENERIC_variables__retrieve__database_to_runtime(const std::string& modname, char* &error, int& cols, int& affected_rows, SQLite3_result* &resultset); + void flush_GENERIC_variables__process__database_to_runtime( + const std::string& modname, SQLite3DB *db, SQLite3_result* resultset, + const bool& lock, const bool& replace, + const std::unordered_set& variables_read_only, + const std::unordered_set& variables_to_delete_silently, + const std::unordered_set& variables_deprecated, + const std::unordered_set& variables_special_values, + std::function special_variable_action = nullptr + ); + char **get_variables_list(); bool set_variable(char *name, char *value, bool lock = true); void flush_admin_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum = "", const time_t epoch = 0, bool lock = true); @@ -394,6 +408,8 @@ class ProxySQL_Admin { void public_add_active_users(enum cred_username_type usertype, char *user=NULL) { __add_active_users(usertype, user); } + // @brief True if all ProxySQL modules have been already started. End of 'phase3'. + bool all_modules_started; ProxySQL_Admin(); ~ProxySQL_Admin(); SQLite3DB *admindb; // in memory @@ -416,6 +432,18 @@ class ProxySQL_Admin { */ bool init(const bootstrap_info_t& bootstrap_info); void init_ldap(); + /** @brief Initializes the HTTP server. For safety should be called after 'phase3'. */ + void init_http_server(); + /** + * @brief Loads the HTTP server config to runtime if all modules are ready, no-op otherwise. + * @details Modules ready when 'all_modules_started=true'. See 'all_modules_started'. + */ + void load_http_server(); + /** + * @brief Loads the RESTAPI server config to runtime if all modules are ready, no-op otherwise. + * @details Modules ready when 'all_modules_started=true'. See 'all_modules_started'. + */ + void load_restapi_server(); bool get_read_only() { return variables.admin_read_only; } bool set_read_only(bool ro) { variables.admin_read_only=ro; return variables.admin_read_only; } bool has_variable(const char *name); diff --git a/include/proxysql_structs.h b/include/proxysql_structs.h index 28f98ac8c5..f3d322305c 100644 --- a/include/proxysql_structs.h +++ b/include/proxysql_structs.h @@ -916,7 +916,7 @@ __thread int mysql_thread___monitor_groupreplication_healthcheck_interval; __thread int mysql_thread___monitor_groupreplication_healthcheck_timeout; __thread int mysql_thread___monitor_groupreplication_healthcheck_max_timeout_count; __thread int mysql_thread___monitor_groupreplication_max_transactions_behind_count; -__thread int mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only; +__thread int mysql_thread___monitor_groupreplication_max_transactions_behind_for_read_only; __thread int mysql_thread___monitor_galera_healthcheck_interval; __thread int mysql_thread___monitor_galera_healthcheck_timeout; __thread int mysql_thread___monitor_galera_healthcheck_max_timeout_count; @@ -1087,7 +1087,7 @@ extern __thread int mysql_thread___monitor_replication_lag_count; extern __thread int mysql_thread___monitor_groupreplication_healthcheck_interval; extern __thread int mysql_thread___monitor_groupreplication_healthcheck_timeout; extern __thread int mysql_thread___monitor_groupreplication_healthcheck_max_timeout_count; -extern __thread int mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only; +extern __thread int mysql_thread___monitor_groupreplication_max_transactions_behind_for_read_only; extern __thread int mysql_thread___monitor_groupreplication_max_transactions_behind_count; extern __thread int mysql_thread___monitor_galera_healthcheck_interval; extern __thread int mysql_thread___monitor_galera_healthcheck_timeout; diff --git a/include/proxysql_typedefs.h b/include/proxysql_typedefs.h new file mode 100644 index 0000000000..f88af0def8 --- /dev/null +++ b/include/proxysql_typedefs.h @@ -0,0 +1,5 @@ +#ifndef PROXYSQL_COMMON_TYPEDEF +#define PROXYSQL_COMMON_TYPEDEF +typedef std::unordered_map umap_query_digest; +typedef std::unordered_map umap_query_digest_text; +#endif // PROXYSQL_COMMON_TYPEDEF diff --git a/include/query_processor.h b/include/query_processor.h index c9dd826a3d..9d62330291 100644 --- a/include/query_processor.h +++ b/include/query_processor.h @@ -10,15 +10,13 @@ #define DIGEST_STATS_FAST_MINSIZE 100000 #define DIGEST_STATS_FAST_THREADS 4 - +#include "../deps/json/json.hpp" #include "khash.h" KHASH_MAP_INIT_STR(khStrInt, int) -typedef std::unordered_map umap_query_digest; -typedef std::unordered_map umap_query_digest_text; - +#include "proxysql_typedefs.h" #define WUS_NOT_FOUND 0 // couldn't find any filter #define WUS_OFF 1 // allow the query @@ -206,6 +204,20 @@ class Query_Processor_Output { free(comment); } } + void get_info_json(nlohmann::json& j) { + j["create_new_connection"] = create_new_conn; + j["reconnect"] = reconnect; + j["sticky_conn"] = sticky_conn; + j["cache_timeout"] = cache_timeout; + j["cache_ttl"] = cache_ttl; + j["delay"] = delay; + j["destination_hostgroup"] = destination_hostgroup; + j["firewall_whitelist_mode"] = firewall_whitelist_mode; + j["multiplex"] = multiplex; + j["timeout"] = timeout; + j["retries"] = retries; + j["max_lag_ms"] = max_lag_ms; + } }; static char *commands_counters_desc[MYSQL_COM_QUERY___NONE]; diff --git a/lib/GTID_Server_Data.cpp b/lib/GTID_Server_Data.cpp new file mode 100644 index 0000000000..d721bfd1b0 --- /dev/null +++ b/lib/GTID_Server_Data.cpp @@ -0,0 +1,463 @@ +#include "MySQL_HostGroups_Manager.h" + +#include "ev.h" +#include + + +extern ProxySQL_Admin *GloAdmin; + +extern MySQL_Threads_Handler *GloMTH; + +extern MySQL_Monitor *GloMyMon; + +static pthread_mutex_t ev_loop_mutex = PTHREAD_MUTEX_INITIALIZER; + +static void gtid_async_cb(struct ev_loop *loop, struct ev_async *watcher, int revents) { + if (glovars.shutdown) { + ev_break(loop); + } + pthread_mutex_lock(&ev_loop_mutex); + MyHGM->gtid_missing_nodes = false; + MyHGM->generate_mysql_gtid_executed_tables(); + pthread_mutex_unlock(&ev_loop_mutex); + return; +} + +static void gtid_timer_cb (struct ev_loop *loop, struct ev_timer *timer, int revents) { + if (GloMTH == nullptr) { return; } + ev_timer_stop(loop, timer); + ev_timer_set(timer, __sync_add_and_fetch(&GloMTH->variables.binlog_reader_connect_retry_msec,0)/1000, 0); + if (glovars.shutdown) { + ev_break(loop); + } + if (MyHGM->gtid_missing_nodes) { + pthread_mutex_lock(&ev_loop_mutex); + MyHGM->gtid_missing_nodes = false; + MyHGM->generate_mysql_gtid_executed_tables(); + pthread_mutex_unlock(&ev_loop_mutex); + } + ev_timer_start(loop, timer); + return; +} + +void reader_cb(struct ev_loop *loop, struct ev_io *w, int revents) { + pthread_mutex_lock(&ev_loop_mutex); + if (revents & EV_READ) { + GTID_Server_Data *sd = (GTID_Server_Data *)w->data; + bool rc = true; + rc = sd->readall(); + if (rc == false) { + //delete sd; + std::string s1 = sd->address; + s1.append(":"); + s1.append(std::to_string(sd->mysql_port)); + MyHGM->gtid_missing_nodes = true; + proxy_warning("GTID: failed to connect to ProxySQL binlog reader on port %d for server %s:%d\n", sd->port, sd->address, sd->mysql_port); + std::unordered_map ::iterator it2; + it2 = MyHGM->gtid_map.find(s1); + if (it2 != MyHGM->gtid_map.end()) { + //MyHGM->gtid_map.erase(it2); + it2->second = NULL; + delete sd; + } + ev_io_stop(MyHGM->gtid_ev_loop, w); + free(w); + } else { + sd->dump(); + } + } + pthread_mutex_unlock(&ev_loop_mutex); +} + +void connect_cb(EV_P_ ev_io *w, int revents) { + pthread_mutex_lock(&ev_loop_mutex); + struct ev_io * c = w; + if (revents & EV_WRITE) { + int optval = 0; + socklen_t optlen = sizeof(optval); + if ((getsockopt(w->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) || + (optval != 0)) { + /* Connection failed; try the next address in the list. */ + //int errnum = optval ? optval : errno; + ev_io_stop(MyHGM->gtid_ev_loop, w); + close(w->fd); + MyHGM->gtid_missing_nodes = true; + GTID_Server_Data * custom_data = (GTID_Server_Data *)w->data; + GTID_Server_Data *sd = custom_data; + std::string s1 = sd->address; + s1.append(":"); + s1.append(std::to_string(sd->mysql_port)); + proxy_warning("GTID: failed to connect to ProxySQL binlog reader on port %d for server %s:%d\n", sd->port, sd->address, sd->mysql_port); + std::unordered_map ::iterator it2; + it2 = MyHGM->gtid_map.find(s1); + if (it2 != MyHGM->gtid_map.end()) { + //MyHGM->gtid_map.erase(it2); + it2->second = NULL; + delete sd; + } + //delete custom_data; + free(c); + } else { + ev_io_stop(MyHGM->gtid_ev_loop, w); + int fd=w->fd; + struct ev_io * new_w = (struct ev_io*) malloc(sizeof(struct ev_io)); + new_w->data = w->data; + GTID_Server_Data * custom_data = (GTID_Server_Data *)new_w->data; + custom_data->w = new_w; + free(w); + ev_io_init(new_w, reader_cb, fd, EV_READ); + ev_io_start(MyHGM->gtid_ev_loop, new_w); + } + } + pthread_mutex_unlock(&ev_loop_mutex); +} + +struct ev_io * new_connector(char *address, uint16_t gtid_port, uint16_t mysql_port) { + int s; + + if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + perror("socket"); + close(s); + return NULL; + } + + ioctl_FIONBIO(s,1); + + struct addrinfo hints; + struct addrinfo *res = NULL; + memset(&hints, 0, sizeof(hints)); + hints.ai_protocol= IPPROTO_TCP; + hints.ai_family= AF_UNSPEC; + hints.ai_socktype= SOCK_STREAM; + + char str_port[NI_MAXSERV+1]; + sprintf(str_port,"%d", gtid_port); + + int gai_rc = getaddrinfo(address, str_port, &hints, &res); + if (gai_rc) { + freeaddrinfo(res); + //exit here + return NULL; + } + + int status = connect(s, res->ai_addr, res->ai_addrlen); + + // Free linked list + freeaddrinfo(res); + + if ((status == 0) || ((status == -1) && (errno == EINPROGRESS))) { + struct ev_io *c = (struct ev_io *)malloc(sizeof(struct ev_io)); + if (c) { + ev_io_init(c, connect_cb, s, EV_WRITE); + GTID_Server_Data * custom_data = new GTID_Server_Data(c, address, gtid_port, mysql_port); + c->data = (void *)custom_data; + return c; + } + /* else error */ + } + return NULL; +} + + + +GTID_Server_Data::GTID_Server_Data(struct ev_io *_w, char *_address, uint16_t _port, uint16_t _mysql_port) { + active = true; + w = _w; + size = 1024; // 1KB buffer + data = (char *)malloc(size); + memset(uuid_server, 0, sizeof(uuid_server)); + pos = 0; + len = 0; + address = strdup(_address); + port = _port; + mysql_port = _mysql_port; + events_read = 0; +} + +void GTID_Server_Data::resize(size_t _s) { + char *data_ = (char *)malloc(_s); + memcpy(data_, data, (_s > size ? size : _s)); + size = _s; + free(data); + data = data_; +} + +GTID_Server_Data::~GTID_Server_Data() { + free(address); + free(data); +} + +bool GTID_Server_Data::readall() { + bool ret = true; + if (size == len) { + // buffer is full, expand + resize(len*2); + } + int rc = 0; + rc = read(w->fd,data+len,size-len); + if (rc > 0) { + len += rc; + } else { + int myerr = errno; + proxy_error("Read returned %d bytes, error %d\n", rc, myerr); + if ( + (rc == 0) || + (rc==-1 && myerr != EINTR && myerr != EAGAIN) + ) { + ret = false; + } + } + return ret; +} + + +bool GTID_Server_Data::gtid_exists(char *gtid_uuid, uint64_t gtid_trxid) { + std::string s = gtid_uuid; + auto it = gtid_executed.find(s); +// fprintf(stderr,"Checking if server %s:%d has GTID %s:%lu ... ", address, port, gtid_uuid, gtid_trxid); + if (it == gtid_executed.end()) { +// fprintf(stderr,"NO\n"); + return false; + } + for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) { + if ((int64_t)gtid_trxid >= itr->first && (int64_t)gtid_trxid <= itr->second) { +// fprintf(stderr,"YES\n"); + return true; + } + } +// fprintf(stderr,"NO\n"); + return false; +} + +void GTID_Server_Data::read_all_gtids() { + while (read_next_gtid()) { + } + } + +void GTID_Server_Data::dump() { + if (len==0) { + return; + } + read_all_gtids(); + //int rc = write(1,data+pos,len-pos); + fflush(stdout); + ///pos += rc; + if (pos >= len/2) { + memmove(data,data+pos,len-pos); + len = len-pos; + pos = 0; + } +} + +bool GTID_Server_Data::writeout() { + bool ret = true; + if (len==0) { + return ret; + } + int rc = 0; + rc = write(w->fd,data+pos,len-pos); + if (rc > 0) { + pos += rc; + if (pos >= len/2) { + memmove(data,data+pos,len-pos); + len = len-pos; + pos = 0; + } + } + return ret; +} + +bool GTID_Server_Data::read_next_gtid() { + if (len==0) { + return false; + } + void *nlp = NULL; + nlp = memchr(data+pos,'\n',len-pos); + if (nlp == NULL) { + return false; + } + int l = (char *)nlp - (data+pos); + char rec_msg[80]; + if (strncmp(data+pos,(char *)"ST=",3)==0) { + // we are reading the bootstrap + char *bs = (char *)malloc(l+1-3); // length + 1 (null byte) - 3 (header) + memcpy(bs, data+pos+3, l-3); + bs[l-3] = '\0'; + char *saveptr1=NULL; + char *saveptr2=NULL; + //char *saveptr3=NULL; + char *token = NULL; + char *subtoken = NULL; + //char *subtoken2 = NULL; + char *str1 = NULL; + char *str2 = NULL; + //char *str3 = NULL; + for (str1 = bs; ; str1 = NULL) { + token = strtok_r(str1, ",", &saveptr1); + if (token == NULL) { + break; + } + int j = 0; + for (str2 = token; ; str2 = NULL) { + subtoken = strtok_r(str2, ":", &saveptr2); + if (subtoken == NULL) { + break; + } + j++; + if (j%2 == 1) { // we are reading the uuid + char *p = uuid_server; + for (unsigned int k=0; kfirst; + s.insert(8,"-"); + s.insert(13,"-"); + s.insert(18,"-"); + s.insert(23,"-"); + s = s + ":"; + for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) { + std::string s2 = s; + s2 = s2 + std::to_string(itr->first); + s2 = s2 + "-"; + s2 = s2 + std::to_string(itr->second); + s2 = s2 + ","; + gtid_set = gtid_set + s2; + } + } + // Extract latest comma only in case 'gtid_executed' isn't empty + if (gtid_set.empty() == false) { + gtid_set.pop_back(); + } + return gtid_set; +} + + + +void addGtid(const gtid_t& gtid, gtid_set_t& gtid_executed) { + auto it = gtid_executed.find(gtid.first); + if (it == gtid_executed.end()) + { + gtid_executed[gtid.first].emplace_back(gtid.second, gtid.second); + return; + } + + bool flag = true; + for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) + { + if (gtid.second >= itr->first && gtid.second <= itr->second) + return; + if (gtid.second + 1 == itr->first) + { + --itr->first; + flag = false; + break; + } + else if (gtid.second == itr->second + 1) + { + ++itr->second; + flag = false; + break; + } + else if (gtid.second < itr->first) + { + it->second.emplace(itr, gtid.second, gtid.second); + return; + } + } + + if (flag) + it->second.emplace_back(gtid.second, gtid.second); + + for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) + { + auto next_itr = std::next(itr); + if (next_itr != it->second.end() && itr->second + 1 == next_itr->first) + { + itr->second = next_itr->second; + it->second.erase(next_itr); + break; + } + } +} + +void * GTID_syncer_run() { + //struct ev_loop * gtid_ev_loop; + //gtid_ev_loop = NULL; + MyHGM->gtid_ev_loop = ev_loop_new (EVBACKEND_POLL | EVFLAG_NOENV); + if (MyHGM->gtid_ev_loop == NULL) { + proxy_error("could not initialise GTID sync loop\n"); + exit(EXIT_FAILURE); + } + //ev_async_init(gtid_ev_async, gtid_async_cb); + //ev_async_start(gtid_ev_loop, gtid_ev_async); + MyHGM->gtid_ev_timer = (struct ev_timer *)malloc(sizeof(struct ev_timer)); + ev_async_init(MyHGM->gtid_ev_async, gtid_async_cb); + ev_async_start(MyHGM->gtid_ev_loop, MyHGM->gtid_ev_async); + //ev_timer_init(MyHGM->gtid_ev_timer, gtid_timer_cb, __sync_add_and_fetch(&GloMTH->variables.binlog_reader_connect_retry_msec,0)/1000, 0); + ev_timer_init(MyHGM->gtid_ev_timer, gtid_timer_cb, 3, 0); + ev_timer_start(MyHGM->gtid_ev_loop, MyHGM->gtid_ev_timer); + //ev_ref(gtid_ev_loop); + ev_run(MyHGM->gtid_ev_loop, 0); + //sleep(1000); + return NULL; +} + diff --git a/lib/Makefile b/lib/Makefile index 62d0544ffe..325b5bd80c 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -127,6 +127,9 @@ default: libproxysql.a _OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo SpookyV2.oo MySQL_Authentication.oo gen_utils.oo sqlite3db.oo mysql_connection.oo MySQL_HostGroups_Manager.oo mysql_data_stream.oo MySQL_Thread.oo MySQL_Session.oo MySQL_Protocol.oo mysql_backend.oo Query_Processor.oo ProxySQL_Admin.oo ProxySQL_Config.oo ProxySQL_Restapi.oo MySQL_Monitor.oo MySQL_Logger.oo thread.oo MySQL_PreparedStatement.oo ProxySQL_Cluster.oo ClickHouse_Authentication.oo ClickHouse_Server.oo ProxySQL_Statistics.oo Chart_bundle_js.oo ProxySQL_HTTP_Server.oo ProxySQL_RESTAPI_Server.oo font-awesome.min.css.oo main-bundle.min.css.oo set_parser.oo MySQL_Variables.oo c_tokenizer.oo proxysql_utils.oo proxysql_coredump.oo proxysql_sslkeylog.oo \ sha256crypt.oo \ + QP_rule_text.oo QP_query_digest_stats.oo \ + GTID_Server_Data.oo MyHGC.oo MySrvConnList.oo MySrvList.oo MySrvC.oo \ + MySQL_encode.oo MySQL_ResultSet.oo \ proxysql_find_charset.oo ProxySQL_Poll.oo OBJ_CXX := $(patsubst %,$(ODIR)/%,$(_OBJ_CXX)) HEADERS := ../include/*.h ../include/*.hpp diff --git a/lib/MyHGC.cpp b/lib/MyHGC.cpp new file mode 100644 index 0000000000..72ce81f746 --- /dev/null +++ b/lib/MyHGC.cpp @@ -0,0 +1,422 @@ +#include "MySQL_HostGroups_Manager.h" + +#ifdef TEST_AURORA +static unsigned long long array_mysrvc_total = 0; +static unsigned long long array_mysrvc_cands = 0; +#endif // TEST_AURORA + +extern MySQL_Threads_Handler *GloMTH; + +MyHGC::MyHGC(int _hid) { + hid=_hid; + mysrvs=new MySrvList(this); + current_time_now = 0; + new_connections_now = 0; + attributes.initialized = false; + reset_attributes(); + // Uninitialized server defaults. Should later be initialized via 'mysql_hostgroup_attributes'. + servers_defaults.weight = -1; + servers_defaults.max_connections = -1; + servers_defaults.use_ssl = -1; + num_online_servers.store(0, std::memory_order_relaxed);; + last_log_time_num_online_servers = 0; +} + +void MyHGC::reset_attributes() { + if (attributes.initialized == false) { + attributes.init_connect = NULL; + attributes.comment = NULL; + attributes.ignore_session_variables_text = NULL; + } + attributes.initialized = true; + attributes.configured = false; + attributes.max_num_online_servers = 1000000; + attributes.throttle_connections_per_sec = 1000000; + attributes.autocommit = -1; + attributes.free_connections_pct = 10; + attributes.handle_warnings = -1; + attributes.multiplex = true; + attributes.connection_warming = false; + free(attributes.init_connect); + attributes.init_connect = NULL; + free(attributes.comment); + attributes.comment = NULL; + free(attributes.ignore_session_variables_text); + attributes.ignore_session_variables_text = NULL; + attributes.ignore_session_variables_json = json(); +} + +MyHGC::~MyHGC() { + reset_attributes(); // free all memory + delete mysrvs; +} + +MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess) { + MySrvC *mysrvc=NULL; + unsigned int j; + unsigned int sum=0; + unsigned int TotalUsedConn=0; + unsigned int l=mysrvs->cnt(); + static time_t last_hg_log = 0; +#ifdef TEST_AURORA + unsigned long long a1 = array_mysrvc_total/10000; + array_mysrvc_total += l; + unsigned long long a2 = array_mysrvc_total/10000; + if (a2 > a1) { + fprintf(stderr, "Total: %llu, Candidates: %llu\n", array_mysrvc_total-l, array_mysrvc_cands); + } +#endif // TEST_AURORA + MySrvC *mysrvcCandidates_static[32]; + MySrvC **mysrvcCandidates = mysrvcCandidates_static; + unsigned int num_candidates = 0; + bool max_connections_reached = false; + if (l>32) { + mysrvcCandidates = (MySrvC **)malloc(sizeof(MySrvC *)*l); + } + if (l) { + //int j=0; + for (j=0; jidx(j); + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { // consider this server only if ONLINE + if (mysrvc->myhgc->num_online_servers.load(std::memory_order_relaxed) <= mysrvc->myhgc->attributes.max_num_online_servers) { // number of online servers in HG is within configured range + if (mysrvc->ConnectionsUsed->conns_length() < mysrvc->max_connections) { // consider this server only if didn't reach max_connections + if (mysrvc->current_latency_us < (mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000)) { // consider the host only if not too far + if (gtid_trxid) { + if (MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } else { + if (max_lag_ms >= 0) { + if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us / 1000) { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } else { + sess->thread->status_variables.stvar[st_var_aws_aurora_replicas_skipped_during_query]++; + } + } else { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } + } + } else { + max_connections_reached = true; + } + } else { + mysrvc->myhgc->log_num_online_server_count_error(); + } + } else { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED) { + // try to recover shunned servers + if (mysrvc->shunned_automatic && mysql_thread___shun_recovery_time_sec) { + time_t t; + t=time(NULL); + // we do all these changes without locking . We assume the server is not used from long + // even if the server is still in used and any of the follow command fails it is not critical + // because this is only an attempt to recover a server that is probably dead anyway + + // the next few lines of code try to solve issue #530 + int max_wait_sec = ( mysql_thread___shun_recovery_time_sec * 1000 >= mysql_thread___connect_timeout_server_max ? mysql_thread___connect_timeout_server_max/1000 - 1 : mysql_thread___shun_recovery_time_sec ); + if (max_wait_sec < 1) { // min wait time should be at least 1 second + max_wait_sec = 1; + } + if (t > mysrvc->time_last_detected_error && (t - mysrvc->time_last_detected_error) > max_wait_sec) { + if ( + (mysrvc->shunned_and_kill_all_connections==false) // it is safe to bring it back online + || + (mysrvc->shunned_and_kill_all_connections==true && mysrvc->ConnectionsUsed->conns_length()==0 && mysrvc->ConnectionsFree->conns_length()==0) // if shunned_and_kill_all_connections is set, ensure all connections are already dropped + ) { +#ifdef DEBUG + if (GloMTH->variables.hostgroup_manager_verbose >= 3) { + proxy_info("Unshunning server %s:%d.\n", mysrvc->address, mysrvc->port); + } +#endif + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); + mysrvc->shunned_automatic=false; + mysrvc->shunned_and_kill_all_connections=false; + mysrvc->connect_ERR_at_time_last_detected_error=0; + mysrvc->time_last_detected_error=0; + // note: the following function scans all the hostgroups. + // This is ok for now because we only have a global mutex. + // If one day we implement a mutex per hostgroup (unlikely, + // but possible), this must be taken into consideration + if (mysql_thread___unshun_algorithm == 1) { + MyHGM->unshun_server_all_hostgroups(mysrvc->address, mysrvc->port, t, max_wait_sec, &mysrvc->myhgc->hid); + } + // if a server is taken back online, consider it immediately + if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000 ) ) { // consider the host only if not too far + if (gtid_trxid) { + if (MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } else { + if (max_lag_ms >= 0) { + if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } else { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } + } + } + } + } + } + } + } + if (max_lag_ms > 0) { // we are using AWS Aurora, as this logic is implemented only here + unsigned int min_num_replicas = sess->thread->variables.aurora_max_lag_ms_only_read_from_replicas; + if (min_num_replicas) { + if (num_candidates >= min_num_replicas) { // there are at least N replicas + // we try to remove the writer + unsigned int total_aws_aurora_current_lag_us=0; + for (j=0; jaws_aurora_current_lag_us; + } + if (total_aws_aurora_current_lag_us) { // we are just double checking that we don't have all servers with aws_aurora_current_lag_us==0 + for (j=0; jaws_aurora_current_lag_us==0) { + sum-=mysrvc->weight; + TotalUsedConn-=mysrvc->ConnectionsUsed->conns_length(); + if (j < num_candidates-1) { + mysrvcCandidates[j]=mysrvcCandidates[num_candidates-1]; + } + num_candidates--; + } + } + } + } + } + } + if (sum==0) { + // per issue #531 , we try a desperate attempt to bring back online any shunned server + // we do this lowering the maximum wait time to 10% + // most of the follow code is copied from few lines above + time_t t; + t=time(NULL); + int max_wait_sec = ( mysql_thread___shun_recovery_time_sec * 1000 >= mysql_thread___connect_timeout_server_max ? mysql_thread___connect_timeout_server_max/10000 - 1 : mysql_thread___shun_recovery_time_sec/10 ); + if (max_wait_sec < 1) { // min wait time should be at least 1 second + max_wait_sec = 1; + } + if (t - last_hg_log > 1) { // log this at most once per second to avoid spamming the logs + last_hg_log = time(NULL); + + if (gtid_trxid) { + proxy_error("Hostgroup %u has no servers ready for GTID '%s:%ld'. Waiting for replication...\n", hid, gtid_uuid, gtid_trxid); + } else { + proxy_error("Hostgroup %u has no servers available%s! Checking servers shunned for more than %u second%s\n", hid, + (max_connections_reached ? " or max_connections reached for all servers" : ""), max_wait_sec, max_wait_sec == 1 ? "" : "s"); + } + } + for (j=0; jidx(j); + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED && mysrvc->shunned_automatic == true) { + if ((t - mysrvc->time_last_detected_error) > max_wait_sec) { + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); + mysrvc->shunned_automatic=false; + mysrvc->connect_ERR_at_time_last_detected_error=0; + mysrvc->time_last_detected_error=0; + // if a server is taken back online, consider it immediately + if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000 ) ) { // consider the host only if not too far + if (gtid_trxid) { + if (MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } else { + if (max_lag_ms >= 0) { + if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } else { + sum+=mysrvc->weight; + TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); + mysrvcCandidates[num_candidates]=mysrvc; + num_candidates++; + } + } + } + } + } + } + } + if (sum==0) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC NULL because no backend ONLINE or with weight\n"); + if (l>32) { + free(mysrvcCandidates); + } +#ifdef TEST_AURORA + array_mysrvc_cands += num_candidates; +#endif // TEST_AURORA + return NULL; // if we reach here, we couldn't find any target + } + +/* + unsigned int New_sum=0; + unsigned int New_TotalUsedConn=0; + // we will now scan again to ignore overloaded servers + for (j=0; jConnectionsUsed->conns_length(); + if ((len * sum) <= (TotalUsedConn * mysrvc->weight * 1.5 + 1)) { + + New_sum+=mysrvc->weight; + New_TotalUsedConn+=len; + } else { + // remove the candidate + if (j+1 < num_candidates) { + mysrvcCandidates[j] = mysrvcCandidates[num_candidates-1]; + } + j--; + num_candidates--; + } + } +*/ + + unsigned int New_sum=sum; + + if (New_sum==0) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC NULL because no backend ONLINE or with weight\n"); + if (l>32) { + free(mysrvcCandidates); + } +#ifdef TEST_AURORA + array_mysrvc_cands += num_candidates; +#endif // TEST_AURORA + return NULL; // if we reach here, we couldn't find any target + } + + // latency awareness algorithm is enabled only when compiled with USE_MYSRVC_ARRAY + if (sess && sess->thread->variables.min_num_servers_lantency_awareness) { + if ((int) num_candidates >= sess->thread->variables.min_num_servers_lantency_awareness) { + unsigned int servers_with_latency = 0; + unsigned int total_latency_us = 0; + // scan and verify that all servers have some latency + for (j=0; jcurrent_latency_us) { + servers_with_latency++; + total_latency_us += mysrvc->current_latency_us; + } + } + if (servers_with_latency == num_candidates) { + // all servers have some latency. + // That is good. If any server have no latency, something is wrong + // and we will skip this algorithm + sess->thread->status_variables.stvar[st_var_ConnPool_get_conn_latency_awareness]++; + unsigned int avg_latency_us = 0; + avg_latency_us = total_latency_us/num_candidates; + for (j=0; jcurrent_latency_us > avg_latency_us) { + // remove the candidate + if (j+1 < num_candidates) { + mysrvcCandidates[j] = mysrvcCandidates[num_candidates-1]; + } + j--; + num_candidates--; + } + } + // we scan again to adjust weight + New_sum = 0; + for (j=0; jweight; + } + } + } + } + + + unsigned int k; + if (New_sum > 32768) { + k=rand()%New_sum; + } else { + k=fastrand()%New_sum; + } + k++; + New_sum=0; + + for (j=0; jweight; + if (k<=New_sum) { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC %p, server %s:%d\n", mysrvc, mysrvc->address, mysrvc->port); + if (l>32) { + free(mysrvcCandidates); + } +#ifdef TEST_AURORA + array_mysrvc_cands += num_candidates; +#endif // TEST_AURORA + return mysrvc; + } + } + } else { + time_t t = time(NULL); + + if (t - last_hg_log > 1) { + last_hg_log = time(NULL); + proxy_error("Hostgroup %u has no servers available!\n", hid); + } + } + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC NULL\n"); + if (l>32) { + free(mysrvcCandidates); + } +#ifdef TEST_AURORA + array_mysrvc_cands += num_candidates; +#endif // TEST_AURORA + return NULL; // if we reach here, we couldn't find any target +} + +void MyHGC::refresh_online_server_count() { + if (__sync_fetch_and_add(&glovars.shutdown, 0) != 0) + return; +#ifdef DEBUG + assert(MyHGM->is_locked); +#endif + unsigned int online_servers_count = 0; + for (unsigned int i = 0; i < mysrvs->servers->len; i++) { + MySrvC* mysrvc = (MySrvC*)mysrvs->servers->index(i); + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { + online_servers_count++; + } + } + num_online_servers.store(online_servers_count, std::memory_order_relaxed); +} + +void MyHGC::log_num_online_server_count_error() { + const time_t curtime = time(NULL); + // if this is the first time the method is called or if more than 10 seconds have passed since the last log + if (last_log_time_num_online_servers == 0 || + ((curtime - last_log_time_num_online_servers) > 10)) { + last_log_time_num_online_servers = curtime; + proxy_error( + "Number of online servers detected in a hostgroup exceeds the configured maximum online servers. hostgroup:%u, num_online_servers:%u, max_online_servers:%u\n", + hid, num_online_servers.load(std::memory_order_relaxed), attributes.max_num_online_servers); + } +} diff --git a/lib/MySQL_HostGroups_Manager.cpp b/lib/MySQL_HostGroups_Manager.cpp index b185416af8..3588591041 100644 --- a/lib/MySQL_HostGroups_Manager.cpp +++ b/lib/MySQL_HostGroups_Manager.cpp @@ -31,10 +31,6 @@ using std::function; -#ifdef TEST_AURORA -static unsigned long long array_mysrvc_total = 0; -static unsigned long long array_mysrvc_cands = 0; -#endif // TEST_AURORA #define SAFE_SQLITE3_STEP(_stmt) do {\ do {\ @@ -57,39 +53,9 @@ class MySrvC; class MySrvList; class MyHGC; -//static struct ev_async * gtid_ev_async; - -static pthread_mutex_t ev_loop_mutex; +struct ev_io * new_connector(char *address, uint16_t gtid_port, uint16_t mysql_port); +void * GTID_syncer_run(); -//static std::unordered_map gtid_map; - -static void gtid_async_cb(struct ev_loop *loop, struct ev_async *watcher, int revents) { - if (glovars.shutdown) { - ev_break(loop); - } - pthread_mutex_lock(&ev_loop_mutex); - MyHGM->gtid_missing_nodes = false; - MyHGM->generate_mysql_gtid_executed_tables(); - pthread_mutex_unlock(&ev_loop_mutex); - return; -} - -static void gtid_timer_cb (struct ev_loop *loop, struct ev_timer *timer, int revents) { - if (GloMTH == nullptr) { return; } - ev_timer_stop(loop, timer); - ev_timer_set(timer, __sync_add_and_fetch(&GloMTH->variables.binlog_reader_connect_retry_msec,0)/1000, 0); - if (glovars.shutdown) { - ev_break(loop); - } - if (MyHGM->gtid_missing_nodes) { - pthread_mutex_lock(&ev_loop_mutex); - MyHGM->gtid_missing_nodes = false; - MyHGM->generate_mysql_gtid_executed_tables(); - pthread_mutex_unlock(&ev_loop_mutex); - } - ev_timer_start(loop, timer); - return; -} static int wait_for_mysql(MYSQL *mysql, int status) { struct pollfd pfd; @@ -115,432 +81,6 @@ static int wait_for_mysql(MYSQL *mysql, int status) { } } -void reader_cb(struct ev_loop *loop, struct ev_io *w, int revents) { - pthread_mutex_lock(&ev_loop_mutex); - if (revents & EV_READ) { - GTID_Server_Data *sd = (GTID_Server_Data *)w->data; - bool rc = true; - rc = sd->readall(); - if (rc == false) { - //delete sd; - std::string s1 = sd->address; - s1.append(":"); - s1.append(std::to_string(sd->mysql_port)); - MyHGM->gtid_missing_nodes = true; - proxy_warning("GTID: failed to connect to ProxySQL binlog reader on port %d for server %s:%d\n", sd->port, sd->address, sd->mysql_port); - std::unordered_map ::iterator it2; - it2 = MyHGM->gtid_map.find(s1); - if (it2 != MyHGM->gtid_map.end()) { - //MyHGM->gtid_map.erase(it2); - it2->second = NULL; - delete sd; - } - ev_io_stop(MyHGM->gtid_ev_loop, w); - free(w); - } else { - sd->dump(); - } - } - pthread_mutex_unlock(&ev_loop_mutex); -} - -void connect_cb(EV_P_ ev_io *w, int revents) { - pthread_mutex_lock(&ev_loop_mutex); - struct ev_io * c = w; - if (revents & EV_WRITE) { - int optval = 0; - socklen_t optlen = sizeof(optval); - if ((getsockopt(w->fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) == -1) || - (optval != 0)) { - /* Connection failed; try the next address in the list. */ - //int errnum = optval ? optval : errno; - ev_io_stop(MyHGM->gtid_ev_loop, w); - close(w->fd); - MyHGM->gtid_missing_nodes = true; - GTID_Server_Data * custom_data = (GTID_Server_Data *)w->data; - GTID_Server_Data *sd = custom_data; - std::string s1 = sd->address; - s1.append(":"); - s1.append(std::to_string(sd->mysql_port)); - proxy_warning("GTID: failed to connect to ProxySQL binlog reader on port %d for server %s:%d\n", sd->port, sd->address, sd->mysql_port); - std::unordered_map ::iterator it2; - it2 = MyHGM->gtid_map.find(s1); - if (it2 != MyHGM->gtid_map.end()) { - //MyHGM->gtid_map.erase(it2); - it2->second = NULL; - delete sd; - } - //delete custom_data; - free(c); - } else { - ev_io_stop(MyHGM->gtid_ev_loop, w); - int fd=w->fd; - struct ev_io * new_w = (struct ev_io*) malloc(sizeof(struct ev_io)); - new_w->data = w->data; - GTID_Server_Data * custom_data = (GTID_Server_Data *)new_w->data; - custom_data->w = new_w; - free(w); - ev_io_init(new_w, reader_cb, fd, EV_READ); - ev_io_start(MyHGM->gtid_ev_loop, new_w); - } - } - pthread_mutex_unlock(&ev_loop_mutex); -} - -struct ev_io * new_connector(char *address, uint16_t gtid_port, uint16_t mysql_port) { - //struct sockaddr_in a; - int s; - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - perror("socket"); - close(s); - return NULL; - } -/* - memset(&a, 0, sizeof(a)); - a.sin_port = htons(gtid_port); - a.sin_family = AF_INET; - if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) { - perror("bad IP address format"); - close(s); - return NULL; - } -*/ - ioctl_FIONBIO(s,1); - - struct addrinfo hints; - struct addrinfo *res = NULL; - memset(&hints, 0, sizeof(hints)); - hints.ai_protocol= IPPROTO_TCP; - hints.ai_family= AF_UNSPEC; - hints.ai_socktype= SOCK_STREAM; - - char str_port[NI_MAXSERV+1]; - sprintf(str_port,"%d", gtid_port); - int gai_rc = getaddrinfo(address, str_port, &hints, &res); - if (gai_rc) { - freeaddrinfo(res); - //exit here - return NULL; - } - - //int status = connect(s, (struct sockaddr *) &a, sizeof(a)); - int status = connect(s, res->ai_addr, res->ai_addrlen); - if ((status == 0) || ((status == -1) && (errno == EINPROGRESS))) { - struct ev_io *c = (struct ev_io *)malloc(sizeof(struct ev_io)); - if (c) { - ev_io_init(c, connect_cb, s, EV_WRITE); - GTID_Server_Data * custom_data = new GTID_Server_Data(c, address, gtid_port, mysql_port); - c->data = (void *)custom_data; - return c; - } - /* else error */ - } - return NULL; -} - - - -GTID_Server_Data::GTID_Server_Data(struct ev_io *_w, char *_address, uint16_t _port, uint16_t _mysql_port) { - active = true; - w = _w; - size = 1024; // 1KB buffer - data = (char *)malloc(size); - memset(uuid_server, 0, sizeof(uuid_server)); - pos = 0; - len = 0; - address = strdup(_address); - port = _port; - mysql_port = _mysql_port; - events_read = 0; -} - -void GTID_Server_Data::resize(size_t _s) { - char *data_ = (char *)malloc(_s); - memcpy(data_, data, (_s > size ? size : _s)); - size = _s; - free(data); - data = data_; -} - -GTID_Server_Data::~GTID_Server_Data() { - free(address); - free(data); -} - -bool GTID_Server_Data::readall() { - bool ret = true; - if (size == len) { - // buffer is full, expand - resize(len*2); - } - int rc = 0; - rc = read(w->fd,data+len,size-len); - if (rc > 0) { - len += rc; - } else { - int myerr = errno; - proxy_error("Read returned %d bytes, error %d\n", rc, myerr); - if ( - (rc == 0) || - (rc==-1 && myerr != EINTR && myerr != EAGAIN) - ) { - ret = false; - } - } - return ret; -} - - -bool GTID_Server_Data::gtid_exists(char *gtid_uuid, uint64_t gtid_trxid) { - std::string s = gtid_uuid; - auto it = gtid_executed.find(s); -// fprintf(stderr,"Checking if server %s:%d has GTID %s:%lu ... ", address, port, gtid_uuid, gtid_trxid); - if (it == gtid_executed.end()) { -// fprintf(stderr,"NO\n"); - return false; - } - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) { - if ((int64_t)gtid_trxid >= itr->first && (int64_t)gtid_trxid <= itr->second) { -// fprintf(stderr,"YES\n"); - return true; - } - } -// fprintf(stderr,"NO\n"); - return false; -} - -void GTID_Server_Data::read_all_gtids() { - while (read_next_gtid()) { - } - } - -void GTID_Server_Data::dump() { - if (len==0) { - return; - } - read_all_gtids(); - //int rc = write(1,data+pos,len-pos); - fflush(stdout); - ///pos += rc; - if (pos >= len/2) { - memmove(data,data+pos,len-pos); - len = len-pos; - pos = 0; - } -} - -bool GTID_Server_Data::writeout() { - bool ret = true; - if (len==0) { - return ret; - } - int rc = 0; - rc = write(w->fd,data+pos,len-pos); - if (rc > 0) { - pos += rc; - if (pos >= len/2) { - memmove(data,data+pos,len-pos); - len = len-pos; - pos = 0; - } - } - return ret; -} - -bool GTID_Server_Data::read_next_gtid() { - if (len==0) { - return false; - } - void *nlp = NULL; - nlp = memchr(data+pos,'\n',len-pos); - if (nlp == NULL) { - return false; - } - int l = (char *)nlp - (data+pos); - char rec_msg[80]; - if (strncmp(data+pos,(char *)"ST=",3)==0) { - // we are reading the bootstrap - char *bs = (char *)malloc(l+1-3); // length + 1 (null byte) - 3 (header) - memcpy(bs, data+pos+3, l-3); - bs[l-3] = '\0'; - char *saveptr1=NULL; - char *saveptr2=NULL; - //char *saveptr3=NULL; - char *token = NULL; - char *subtoken = NULL; - //char *subtoken2 = NULL; - char *str1 = NULL; - char *str2 = NULL; - //char *str3 = NULL; - for (str1 = bs; ; str1 = NULL) { - token = strtok_r(str1, ",", &saveptr1); - if (token == NULL) { - break; - } - int j = 0; - for (str2 = token; ; str2 = NULL) { - subtoken = strtok_r(str2, ":", &saveptr2); - if (subtoken == NULL) { - break; - } - j++; - if (j%2 == 1) { // we are reading the uuid - char *p = uuid_server; - for (unsigned int k=0; kfirst; - s.insert(8,"-"); - s.insert(13,"-"); - s.insert(18,"-"); - s.insert(23,"-"); - s = s + ":"; - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) { - std::string s2 = s; - s2 = s2 + std::to_string(itr->first); - s2 = s2 + "-"; - s2 = s2 + std::to_string(itr->second); - s2 = s2 + ","; - gtid_set = gtid_set + s2; - } - } - // Extract latest comma only in case 'gtid_executed' isn't empty - if (gtid_set.empty() == false) { - gtid_set.pop_back(); - } - return gtid_set; -} - - - -void addGtid(const gtid_t& gtid, gtid_set_t& gtid_executed) { - auto it = gtid_executed.find(gtid.first); - if (it == gtid_executed.end()) - { - gtid_executed[gtid.first].emplace_back(gtid.second, gtid.second); - return; - } - - bool flag = true; - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) - { - if (gtid.second >= itr->first && gtid.second <= itr->second) - return; - if (gtid.second + 1 == itr->first) - { - --itr->first; - flag = false; - break; - } - else if (gtid.second == itr->second + 1) - { - ++itr->second; - flag = false; - break; - } - else if (gtid.second < itr->first) - { - it->second.emplace(itr, gtid.second, gtid.second); - return; - } - } - - if (flag) - it->second.emplace_back(gtid.second, gtid.second); - - for (auto itr = it->second.begin(); itr != it->second.end(); ++itr) - { - auto next_itr = std::next(itr); - if (next_itr != it->second.end() && itr->second + 1 == next_itr->first) - { - itr->second = next_itr->second; - it->second.erase(next_itr); - break; - } - } -} - -static void * GTID_syncer_run() { - //struct ev_loop * gtid_ev_loop; - //gtid_ev_loop = NULL; - MyHGM->gtid_ev_loop = ev_loop_new (EVBACKEND_POLL | EVFLAG_NOENV); - if (MyHGM->gtid_ev_loop == NULL) { - proxy_error("could not initialise GTID sync loop\n"); - exit(EXIT_FAILURE); - } - //ev_async_init(gtid_ev_async, gtid_async_cb); - //ev_async_start(gtid_ev_loop, gtid_ev_async); - MyHGM->gtid_ev_timer = (struct ev_timer *)malloc(sizeof(struct ev_timer)); - ev_async_init(MyHGM->gtid_ev_async, gtid_async_cb); - ev_async_start(MyHGM->gtid_ev_loop, MyHGM->gtid_ev_async); - //ev_timer_init(MyHGM->gtid_ev_timer, gtid_timer_cb, __sync_add_and_fetch(&GloMTH->variables.binlog_reader_connect_retry_msec,0)/1000, 0); - ev_timer_init(MyHGM->gtid_ev_timer, gtid_timer_cb, 3, 0); - ev_timer_start(MyHGM->gtid_ev_loop, MyHGM->gtid_ev_timer); - //ev_ref(gtid_ev_loop); - ev_run(MyHGM->gtid_ev_loop, 0); - //sleep(1000); - return NULL; -} //static void * HGCU_thread_run() { static void * HGCU_thread_run() { @@ -661,266 +201,6 @@ static void * HGCU_thread_run() { } -MySQL_Connection *MySrvConnList::index(unsigned int _k) { - return (MySQL_Connection *)conns->index(_k); -} - -MySQL_Connection * MySrvConnList::remove(int _k) { - return (MySQL_Connection *)conns->remove_index_fast(_k); -} - -/* -unsigned int MySrvConnList::conns_length() { - return conns->len; -} -*/ - -MySrvConnList::MySrvConnList(MySrvC *_mysrvc) { - mysrvc=_mysrvc; - conns=new PtrArray(); -} - -void MySrvConnList::add(MySQL_Connection *c) { - conns->add(c); -} - -MySrvConnList::~MySrvConnList() { - mysrvc=NULL; - while (conns_length()) { - MySQL_Connection *conn=(MySQL_Connection *)conns->remove_index_fast(0); - delete conn; - } - delete conns; -} - -MySrvList::MySrvList(MyHGC *_myhgc) { - myhgc=_myhgc; - servers=new PtrArray(); -} - -void MySrvList::add(MySrvC *s) { - if (s->myhgc==NULL) { - s->myhgc=myhgc; - } - servers->add(s); -} - - -int MySrvList::find_idx(MySrvC *s) { - for (unsigned int i=0; ilen; i++) { - MySrvC *mysrv=(MySrvC *)servers->index(i); - if (mysrv==s) { - return (unsigned int)i; - } - } - return -1; -} - -void MySrvList::remove(MySrvC *s) { - int i=find_idx(s); - assert(i>=0); - servers->remove_index_fast((unsigned int)i); -} - -void MySrvConnList::drop_all_connections() { - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Dropping all connections (%u total) on MySrvConnList %p for server %s:%d , hostgroup=%d , status=%d\n", conns_length(), this, mysrvc->address, mysrvc->port, mysrvc->myhgc->hid, mysrvc->status); - while (conns_length()) { - MySQL_Connection *conn=(MySQL_Connection *)conns->remove_index_fast(0); - delete conn; - } -} - - -MySrvC::MySrvC( - char* add, uint16_t p, uint16_t gp, int64_t _weight, enum MySerStatus _status, unsigned int _compression, - int64_t _max_connections, unsigned int _max_replication_lag, int32_t _use_ssl, unsigned int _max_latency_ms, - char* _comment -) { - address=strdup(add); - port=p; - gtid_port=gp; - weight=_weight; - status=_status; - compression=_compression; - max_connections=_max_connections; - max_replication_lag=_max_replication_lag; - use_ssl=_use_ssl; - cur_replication_lag=0; - cur_replication_lag_count=0; - max_latency_us=_max_latency_ms*1000; - current_latency_us=0; - aws_aurora_current_lag_us = 0; - connect_OK=0; - connect_ERR=0; - queries_sent=0; - bytes_sent=0; - bytes_recv=0; - max_connections_used=0; - queries_gtid_sync=0; - time_last_detected_error=0; - connect_ERR_at_time_last_detected_error=0; - shunned_automatic=false; - shunned_and_kill_all_connections=false; // false to default - //charset=_charset; - myhgc=NULL; - comment=strdup(_comment); - ConnectionsUsed=new MySrvConnList(this); - ConnectionsFree=new MySrvConnList(this); -} - -void MySrvC::connect_error(int err_num, bool get_mutex) { - // NOTE: this function operates without any mutex - // although, it is not extremely important if any counter is lost - // as a single connection failure won't make a significant difference - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Connect failed with code '%d'\n", err_num); - __sync_fetch_and_add(&connect_ERR,1); - __sync_fetch_and_add(&MyHGM->status.server_connections_aborted,1); - if (err_num >= 1048 && err_num <= 1052) - return; - if (err_num >= 1054 && err_num <= 1075) - return; - if (err_num >= 1099 && err_num <= 1104) - return; - if (err_num >= 1106 && err_num <= 1113) - return; - if (err_num >= 1116 && err_num <= 1118) - return; - if (err_num == 1136 || (err_num >= 1138 && err_num <= 1149)) - return; - switch (err_num) { - case 1007: // Can't create database - case 1008: // Can't drop database - case 1044: // access denied - case 1045: // access denied -/* - case 1048: // Column cannot be null - case 1049: // Unknown database - case 1050: // Table already exists - case 1051: // Unknown table - case 1052: // Column is ambiguous -*/ - case 1120: - case 1203: // User %s already has more than 'max_user_connections' active connections - case 1226: // User '%s' has exceeded the '%s' resource (current value: %ld) - case 3118: // Access denied for user '%s'. Account is locked.. - return; - break; - default: - break; - } - time_t t=time(NULL); - if (t > time_last_detected_error) { - time_last_detected_error=t; - connect_ERR_at_time_last_detected_error=1; - } else { - if (t < time_last_detected_error) { - // time_last_detected_error is in the future - // this means that monitor has a ping interval too big and tuned that in the future - return; - } - // same time - /** - * @brief The expected configured retries set by 'mysql-connect_retries_on_failure' + '2' extra expected - * connection errors. - * @details This two extra connections errors are expected: - * 1. An initial connection error generated by the datastream and the connection when being created, - * this is, right after the session has requested a connection to the connection pool. This error takes - * places directly in the state machine from 'MySQL_Connection'. Because of this, we consider this - * additional error to be a consequence of the two states machines, and it's not considered for - * 'connect_retries'. - * 2. A second connection connection error, which is the initial connection error generated by 'MySQL_Session' - * when already in the 'CONNECTING_SERVER' state. This error is an 'extra error' to always consider, since - * it's not part of the retries specified by 'mysql_thread___connect_retries_on_failure', thus, we set the - * 'connect_retries' to be 'mysql_thread___connect_retries_on_failure + 1'. - */ - int connect_retries = mysql_thread___connect_retries_on_failure + 1; - int max_failures = mysql_thread___shun_on_failures > connect_retries ? connect_retries : mysql_thread___shun_on_failures; - - if (__sync_add_and_fetch(&connect_ERR_at_time_last_detected_error,1) >= (unsigned int)max_failures) { - bool _shu=false; - if (get_mutex==true) - MyHGM->wrlock(); // to prevent race conditions, lock here. See #627 - if (status==MYSQL_SERVER_STATUS_ONLINE) { - status=MYSQL_SERVER_STATUS_SHUNNED; - shunned_automatic=true; - _shu=true; - } else { - _shu=false; - } - if (get_mutex==true) - MyHGM->wrunlock(); - if (_shu) { - proxy_error("Shunning server %s:%d with %u errors/sec. Shunning for %u seconds\n", address, port, connect_ERR_at_time_last_detected_error , mysql_thread___shun_recovery_time_sec); - } - } - } -} - -void MySrvC::shun_and_killall() { - status=MYSQL_SERVER_STATUS_SHUNNED; - shunned_automatic=true; - shunned_and_kill_all_connections=true; -} - -MySrvC::~MySrvC() { - if (address) free(address); - if (comment) free(comment); - delete ConnectionsUsed; - delete ConnectionsFree; -} - -MySrvList::~MySrvList() { - myhgc=NULL; - while (servers->len) { - MySrvC *mysrvc=(MySrvC *)servers->remove_index_fast(0); - delete mysrvc; - } - delete servers; -} - - -MyHGC::MyHGC(int _hid) { - hid=_hid; - mysrvs=new MySrvList(this); - current_time_now = 0; - new_connections_now = 0; - attributes.initialized = false; - reset_attributes(); - // Uninitialized server defaults. Should later be initialized via 'mysql_hostgroup_attributes'. - servers_defaults.weight = -1; - servers_defaults.max_connections = -1; - servers_defaults.use_ssl = -1; -} - -void MyHGC::reset_attributes() { - if (attributes.initialized == false) { - attributes.init_connect = NULL; - attributes.comment = NULL; - attributes.ignore_session_variables_text = NULL; - } - attributes.initialized = true; - attributes.configured = false; - attributes.max_num_online_servers = 1000000; - attributes.throttle_connections_per_sec = 1000000; - attributes.autocommit = -1; - attributes.free_connections_pct = 10; - attributes.handle_warnings = -1; - attributes.multiplex = true; - attributes.connection_warming = false; - free(attributes.init_connect); - attributes.init_connect = NULL; - free(attributes.comment); - attributes.comment = NULL; - free(attributes.ignore_session_variables_text); - attributes.ignore_session_variables_text = NULL; - attributes.ignore_session_variables_json = json(); -} - -MyHGC::~MyHGC() { - reset_attributes(); // free all memory - delete mysrvs; -} - using metric_name = std::string; using metric_help = std::string; using metric_tags = std::map; @@ -1296,7 +576,6 @@ hg_metrics_map = std::make_tuple( ); MySQL_HostGroups_Manager::MySQL_HostGroups_Manager() { - pthread_mutex_init(&ev_loop_mutex, NULL); status.client_connections=0; status.client_connections_aborted=0; status.client_connections_created=0; @@ -1441,6 +720,9 @@ void MySQL_HostGroups_Manager::wrlock() { #else spin_wrlock(&rwlock); #endif +#ifdef DEBUG + is_locked = true; +#endif } void MySQL_HostGroups_Manager::p_update_mysql_error_counter(p_mysql_error_type err_type, unsigned int hid, char* address, uint16_t port, unsigned int code) { @@ -1475,6 +757,9 @@ void MySQL_HostGroups_Manager::p_update_mysql_error_counter(p_mysql_error_type e } void MySQL_HostGroups_Manager::wrunlock() { +#ifdef DEBUG + is_locked = false; +#endif #ifdef MHM_PTHREAD_MUTEX pthread_mutex_unlock(&lock); #else @@ -2007,7 +1292,7 @@ bool MySQL_HostGroups_Manager::commit( long long ptr=atoll(r->fields[0]); proxy_warning("Removed server at address %lld, hostgroup %s, address %s port %s. Setting status OFFLINE HARD and immediately dropping all free connections. Used connections will be dropped when trying to use them\n", ptr, r->fields[1], r->fields[2], r->fields[3]); MySrvC *mysrvc=(MySrvC *)ptr; - mysrvc->status=MYSQL_SERVER_STATUS_OFFLINE_HARD; + mysrvc->set_status(MYSQL_SERVER_STATUS_OFFLINE_HARD); mysrvc->ConnectionsFree->drop_all_connections(); char *q1=(char *)"DELETE FROM mysql_servers WHERE mem_pointer=%lld"; char *q2=(char *)malloc(strlen(q1)+32); @@ -2053,14 +1338,14 @@ bool MySQL_HostGroups_Manager::commit( for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { SQLite3_row *r=*it; long long ptr=atoll(r->fields[12]); // increase this index every time a new column is added - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d , weight=%d, status=%d, mem_pointer=%llu, hostgroup=%d, compression=%d\n", r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]), (MySerStatus) atoi(r->fields[5]), ptr, atoi(r->fields[0]), atoi(r->fields[6])); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d , weight=%d, status=%d, mem_pointer=%llu, hostgroup=%d, compression=%d\n", r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]), atoi(r->fields[5]), ptr, atoi(r->fields[0]), atoi(r->fields[6])); //fprintf(stderr,"%lld\n", ptr); if (ptr==0) { if (GloMTH->variables.hostgroup_manager_verbose) { - proxy_info("Creating new server in HG %d : %s:%d , gtid_port=%d, weight=%d, status=%d\n", atoi(r->fields[0]), r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]), atoi(r->fields[4]), (MySerStatus) atoi(r->fields[5])); + proxy_info("Creating new server in HG %d : %s:%d , gtid_port=%d, weight=%d, status=%d\n", atoi(r->fields[0]), r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]), atoi(r->fields[4]), atoi(r->fields[5])); } - MySrvC *mysrvc=new MySrvC(r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]), atoi(r->fields[4]), (MySerStatus) atoi(r->fields[5]), atoi(r->fields[6]), atoi(r->fields[7]), atoi(r->fields[8]), atoi(r->fields[9]), atoi(r->fields[10]), r->fields[11]); // add new fields here if adding more columns in mysql_servers - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Adding new server %s:%d , weight=%d, status=%d, mem_ptr=%p into hostgroup=%d\n", r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]), (MySerStatus) atoi(r->fields[5]), mysrvc, atoi(r->fields[0])); + MySrvC *mysrvc=new MySrvC(r->fields[1], atoi(r->fields[2]), atoi(r->fields[3]), atoi(r->fields[4]), (MySerStatus)atoi(r->fields[5]), atoi(r->fields[6]), atoi(r->fields[7]), atoi(r->fields[8]), atoi(r->fields[9]), atoi(r->fields[10]), r->fields[11]); // add new fields here if adding more columns in mysql_servers + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Adding new server %s:%d , weight=%d, status=%d, mem_ptr=%p into hostgroup=%d\n", r->fields[1], atoi(r->fields[2]), atoi(r->fields[4]), atoi(r->fields[5]), mysrvc, atoi(r->fields[0])); add(mysrvc,atoi(r->fields[0])); ptr=(uintptr_t)mysrvc; rc=(*proxy_sqlite3_bind_int64)(statement1, 1, ptr); ASSERT_SQLITE_OK(rc, mydb); @@ -2093,7 +1378,7 @@ bool MySQL_HostGroups_Manager::commit( if (atoi(r->fields[5])!=atoi(r->fields[15])) { bool change_server_status = true; if (GloMTH->variables.evaluate_replication_lag_on_servers_load == 1) { - if (mysrvc->status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG && // currently server is shunned due to replication lag + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG && // currently server is shunned due to replication lag (MySerStatus)atoi(r->fields[15]) == MYSQL_SERVER_STATUS_ONLINE) { // new server status is online if (mysrvc->cur_replication_lag != -2) { // Master server? Seconds_Behind_Master column is not present const unsigned int new_max_repl_lag = atoi(r->fields[18]); @@ -2107,10 +1392,10 @@ bool MySQL_HostGroups_Manager::commit( } if (change_server_status == true) { if (GloMTH->variables.hostgroup_manager_verbose) - proxy_info("Changing status for server %d:%s:%d (%s:%d) from %d (%d) to %d\n", mysrvc->myhgc->hid, mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[5]), mysrvc->status, atoi(r->fields[15])); - mysrvc->status = (MySerStatus)atoi(r->fields[15]); + proxy_info("Changing status for server %d:%s:%d (%s:%d) from %d (%d) to %d\n", mysrvc->myhgc->hid, mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[5]), (int)mysrvc->get_status(), atoi(r->fields[15])); + mysrvc->set_status((MySerStatus)atoi(r->fields[15])); } - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED) { mysrvc->shunned_automatic=false; } } @@ -2129,11 +1414,11 @@ bool MySQL_HostGroups_Manager::commit( proxy_info("Changing max_replication_lag for server %u:%s:%d (%s:%d) from %d (%d) to %d\n" , mysrvc->myhgc->hid , mysrvc->address, mysrvc->port, r->fields[1], atoi(r->fields[2]), atoi(r->fields[8]) , mysrvc->max_replication_lag , atoi(r->fields[18])); mysrvc->max_replication_lag=atoi(r->fields[18]); if (mysrvc->max_replication_lag == 0) { // we just changed it to 0 - if (mysrvc->status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { // the server is currently shunned due to replication lag // but we reset max_replication_lag to 0 // therefore we immediately reset the status too - mysrvc->status = MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); } } } @@ -2155,7 +1440,7 @@ bool MySQL_HostGroups_Manager::commit( } if (run_update) { rc=(*proxy_sqlite3_bind_int64)(statement2, 1, mysrvc->weight); ASSERT_SQLITE_OK(rc, mydb); - rc=(*proxy_sqlite3_bind_int64)(statement2, 2, mysrvc->status); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement2, 2, (int)mysrvc->get_status()); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement2, 3, mysrvc->compression); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement2, 4, mysrvc->max_connections); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement2, 5, mysrvc->max_replication_lag); ASSERT_SQLITE_OK(rc, mydb); @@ -2405,7 +1690,7 @@ void MySQL_HostGroups_Manager::generate_mysql_gtid_executed_tables() { } if (gtid_is) { gtid_is->active = true; - } else if (mysrvc->status != MYSQL_SERVER_STATUS_OFFLINE_HARD) { + } else if (mysrvc->get_status() != MYSQL_SERVER_STATUS_OFFLINE_HARD) { // we didn't find it. Create it /* struct ev_io *watcher = (struct ev_io *)malloc(sizeof(struct ev_io)); @@ -2460,10 +1745,12 @@ void MySQL_HostGroups_Manager::purge_mysql_servers_table() { MySrvC *mysrvc=NULL; for (unsigned int j=0; jmysrvs->servers->len; j++) { mysrvc=myhgc->mysrvs->idx(j); - if (mysrvc->status==MYSQL_SERVER_STATUS_OFFLINE_HARD) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { if (mysrvc->ConnectionsUsed->conns_length()==0 && mysrvc->ConnectionsFree->conns_length()==0) { // no more connections for OFFLINE_HARD server, removing it mysrvc=(MySrvC *)myhgc->mysrvs->servers->remove_index_fast(j); + // already being refreshed in MySrvC destructor + //myhgc->refresh_online_server_count(); j--; delete mysrvc; } @@ -2513,7 +1800,7 @@ void MySQL_HostGroups_Manager::generate_mysql_servers_table(int *_onlyhg) { mysrvc=myhgc->mysrvs->idx(j); if (mysql_thread___hostgroup_manager_verbose) { char *st; - switch (mysrvc->status) { + switch ((int)mysrvc->get_status()) { case 0: st=(char *)"ONLINE"; break; @@ -2543,7 +1830,7 @@ void MySQL_HostGroups_Manager::generate_mysql_servers_table(int *_onlyhg) { rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+3, mysrvc->port); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+4, mysrvc->gtid_port); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+5, mysrvc->weight); ASSERT_SQLITE_OK(rc, mydb); - rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+6, mysrvc->status); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+6, (int)mysrvc->get_status()); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+7, mysrvc->compression); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+8, mysrvc->max_connections); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement32, (i*13)+9, mysrvc->max_replication_lag); ASSERT_SQLITE_OK(rc, mydb); @@ -2566,7 +1853,7 @@ void MySQL_HostGroups_Manager::generate_mysql_servers_table(int *_onlyhg) { rc=(*proxy_sqlite3_bind_int64)(statement1, 3, mysrvc->port); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement1, 4, mysrvc->gtid_port); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement1, 5, mysrvc->weight); ASSERT_SQLITE_OK(rc, mydb); - rc=(*proxy_sqlite3_bind_int64)(statement1, 6, mysrvc->status); ASSERT_SQLITE_OK(rc, mydb); + rc=(*proxy_sqlite3_bind_int64)(statement1, 6, (int)mysrvc->get_status()); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement1, 7, mysrvc->compression); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement1, 8, mysrvc->max_connections); ASSERT_SQLITE_OK(rc, mydb); rc=(*proxy_sqlite3_bind_int64)(statement1, 9, mysrvc->max_replication_lag); ASSERT_SQLITE_OK(rc, mydb); @@ -3054,7 +2341,7 @@ void MySQL_HostGroups_Manager::push_MyConn_to_pool(MySQL_Connection *c, bool _lo mysrvc = static_cast(c->parent); // Log debug information about the connection being returned to the pool - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, mysrvc->status); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); // Remove the connection from the list of used connections for the parent server mysrvc->ConnectionsUsed->remove(c); @@ -3066,19 +2353,18 @@ void MySQL_HostGroups_Manager::push_MyConn_to_pool(MySQL_Connection *c, bool _lo // If the largest query length exceeds the threshold, destroy the connection if (c->largest_query_length > (unsigned int)GloMTH->variables.threshold_query_length) { - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d . largest_query_length = %lu\n", c, mysrvc->address, mysrvc->port, mysrvc->status, c->largest_query_length); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d . largest_query_length = %lu\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status(), c->largest_query_length); delete c; goto __exit_push_MyConn_to_pool; } // If the server is online and the connection is in the idle state - if (mysrvc->status==MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { if (c->async_state_machine==ASYNC_IDLE) { if (GloMTH == NULL) { goto __exit_push_MyConn_to_pool; } - // Check if the connection has too many prepared statements - if (c->local_stmts->get_num_backend_stmts() > (unsigned int)GloMTH->variables.max_stmts_per_connection) { + if (c->local_stmts->get_num_backend_stmts() > (unsigned int)GloMTH->variables.max_stmts_per_connection) { // Check if the connection has too many prepared statements // Log debug information about destroying the connection due to too many prepared statements - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d because has too many prepared statements\n", c, mysrvc->address, mysrvc->port, mysrvc->status); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d because has too many prepared statements\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); // delete c; mysrvc->ConnectionsUsed->add(c); // Add the connection back to the list of used connections destroy_MyConn_from_pool(c, false); // Destroy the connection from the pool @@ -3088,12 +2374,12 @@ void MySQL_HostGroups_Manager::push_MyConn_to_pool(MySQL_Connection *c, bool _lo } } else { // Log debug information about destroying the connection - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, mysrvc->status); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); delete c; // Destroy the connection } } else { // Log debug information about destroying the connection - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, mysrvc->status); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Destroying MySQL_Connection %p, server %s:%d with status %d\n", c, mysrvc->address, mysrvc->port, (int)mysrvc->get_status()); delete c; // Destroy the connection } @@ -3137,557 +2423,6 @@ void MySQL_HostGroups_Manager::push_MyConn_to_pool_array(MySQL_Connection **ca, wrunlock(); } -MySrvC *MyHGC::get_random_MySrvC(char * gtid_uuid, uint64_t gtid_trxid, int max_lag_ms, MySQL_Session *sess) { - MySrvC *mysrvc=NULL; - unsigned int j; - unsigned int sum=0; - unsigned int TotalUsedConn=0; - unsigned int l=mysrvs->cnt(); - static time_t last_hg_log = 0; -#ifdef TEST_AURORA - unsigned long long a1 = array_mysrvc_total/10000; - array_mysrvc_total += l; - unsigned long long a2 = array_mysrvc_total/10000; - if (a2 > a1) { - fprintf(stderr, "Total: %llu, Candidates: %llu\n", array_mysrvc_total-l, array_mysrvc_cands); - } -#endif // TEST_AURORA - MySrvC *mysrvcCandidates_static[32]; - MySrvC **mysrvcCandidates = mysrvcCandidates_static; - unsigned int num_candidates = 0; - bool max_connections_reached = false; - if (l>32) { - mysrvcCandidates = (MySrvC **)malloc(sizeof(MySrvC *)*l); - } - if (l) { - //int j=0; - for (j=0; jidx(j); - if (mysrvc->status==MYSQL_SERVER_STATUS_ONLINE) { // consider this server only if ONLINE - if (mysrvc->ConnectionsUsed->conns_length() < mysrvc->max_connections) { // consider this server only if didn't reach max_connections - if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000 ) ) { // consider the host only if not too far - if (gtid_trxid) { - if (MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } else { - if (max_lag_ms >= 0) { - if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } else { - sess->thread->status_variables.stvar[st_var_aws_aurora_replicas_skipped_during_query]++; - } - } else { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } - } - } else { - max_connections_reached = true; - } - } else { - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED) { - // try to recover shunned servers - if (mysrvc->shunned_automatic && mysql_thread___shun_recovery_time_sec) { - time_t t; - t=time(NULL); - // we do all these changes without locking . We assume the server is not used from long - // even if the server is still in used and any of the follow command fails it is not critical - // because this is only an attempt to recover a server that is probably dead anyway - - // the next few lines of code try to solve issue #530 - int max_wait_sec = ( mysql_thread___shun_recovery_time_sec * 1000 >= mysql_thread___connect_timeout_server_max ? mysql_thread___connect_timeout_server_max/1000 - 1 : mysql_thread___shun_recovery_time_sec ); - if (max_wait_sec < 1) { // min wait time should be at least 1 second - max_wait_sec = 1; - } - if (t > mysrvc->time_last_detected_error && (t - mysrvc->time_last_detected_error) > max_wait_sec) { - if ( - (mysrvc->shunned_and_kill_all_connections==false) // it is safe to bring it back online - || - (mysrvc->shunned_and_kill_all_connections==true && mysrvc->ConnectionsUsed->conns_length()==0 && mysrvc->ConnectionsFree->conns_length()==0) // if shunned_and_kill_all_connections is set, ensure all connections are already dropped - ) { -#ifdef DEBUG - if (GloMTH->variables.hostgroup_manager_verbose >= 3) { - proxy_info("Unshunning server %s:%d.\n", mysrvc->address, mysrvc->port); - } -#endif - mysrvc->status=MYSQL_SERVER_STATUS_ONLINE; - mysrvc->shunned_automatic=false; - mysrvc->shunned_and_kill_all_connections=false; - mysrvc->connect_ERR_at_time_last_detected_error=0; - mysrvc->time_last_detected_error=0; - // note: the following function scans all the hostgroups. - // This is ok for now because we only have a global mutex. - // If one day we implement a mutex per hostgroup (unlikely, - // but possible), this must be taken into consideration - if (mysql_thread___unshun_algorithm == 1) { - MyHGM->unshun_server_all_hostgroups(mysrvc->address, mysrvc->port, t, max_wait_sec, &mysrvc->myhgc->hid); - } - // if a server is taken back online, consider it immediately - if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000 ) ) { // consider the host only if not too far - if (gtid_trxid) { - if (MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } else { - if (max_lag_ms >= 0) { - if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } else { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } - } - } - } - } - } - } - } - if (max_lag_ms > 0) { // we are using AWS Aurora, as this logic is implemented only here - unsigned int min_num_replicas = sess->thread->variables.aurora_max_lag_ms_only_read_from_replicas; - if (min_num_replicas) { - if (num_candidates >= min_num_replicas) { // there are at least N replicas - // we try to remove the writer - unsigned int total_aws_aurora_current_lag_us=0; - for (j=0; jaws_aurora_current_lag_us; - } - if (total_aws_aurora_current_lag_us) { // we are just double checking that we don't have all servers with aws_aurora_current_lag_us==0 - for (j=0; jaws_aurora_current_lag_us==0) { - sum-=mysrvc->weight; - TotalUsedConn-=mysrvc->ConnectionsUsed->conns_length(); - if (j < num_candidates-1) { - mysrvcCandidates[j]=mysrvcCandidates[num_candidates-1]; - } - num_candidates--; - } - } - } - } - } - } - if (sum==0) { - // per issue #531 , we try a desperate attempt to bring back online any shunned server - // we do this lowering the maximum wait time to 10% - // most of the follow code is copied from few lines above - time_t t; - t=time(NULL); - int max_wait_sec = ( mysql_thread___shun_recovery_time_sec * 1000 >= mysql_thread___connect_timeout_server_max ? mysql_thread___connect_timeout_server_max/10000 - 1 : mysql_thread___shun_recovery_time_sec/10 ); - if (max_wait_sec < 1) { // min wait time should be at least 1 second - max_wait_sec = 1; - } - if (t - last_hg_log > 1) { // log this at most once per second to avoid spamming the logs - last_hg_log = time(NULL); - - if (gtid_trxid) { - proxy_error("Hostgroup %u has no servers ready for GTID '%s:%ld'. Waiting for replication...\n", hid, gtid_uuid, gtid_trxid); - } else { - proxy_error("Hostgroup %u has no servers available%s! Checking servers shunned for more than %u second%s\n", hid, - (max_connections_reached ? " or max_connections reached for all servers" : ""), max_wait_sec, max_wait_sec == 1 ? "" : "s"); - } - } - for (j=0; jidx(j); - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED && mysrvc->shunned_automatic==true) { - if ((t - mysrvc->time_last_detected_error) > max_wait_sec) { - mysrvc->status=MYSQL_SERVER_STATUS_ONLINE; - mysrvc->shunned_automatic=false; - mysrvc->connect_ERR_at_time_last_detected_error=0; - mysrvc->time_last_detected_error=0; - // if a server is taken back online, consider it immediately - if ( mysrvc->current_latency_us < ( mysrvc->max_latency_us ? mysrvc->max_latency_us : mysql_thread___default_max_latency_ms*1000 ) ) { // consider the host only if not too far - if (gtid_trxid) { - if (MyHGM->gtid_exists(mysrvc, gtid_uuid, gtid_trxid)) { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } else { - if (max_lag_ms >= 0) { - if ((unsigned int)max_lag_ms >= mysrvc->aws_aurora_current_lag_us/1000) { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } else { - sum+=mysrvc->weight; - TotalUsedConn+=mysrvc->ConnectionsUsed->conns_length(); - mysrvcCandidates[num_candidates]=mysrvc; - num_candidates++; - } - } - } - } - } - } - } - if (sum==0) { - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC NULL because no backend ONLINE or with weight\n"); - if (l>32) { - free(mysrvcCandidates); - } -#ifdef TEST_AURORA - array_mysrvc_cands += num_candidates; -#endif // TEST_AURORA - return NULL; // if we reach here, we couldn't find any target - } - -/* - unsigned int New_sum=0; - unsigned int New_TotalUsedConn=0; - // we will now scan again to ignore overloaded servers - for (j=0; jConnectionsUsed->conns_length(); - if ((len * sum) <= (TotalUsedConn * mysrvc->weight * 1.5 + 1)) { - - New_sum+=mysrvc->weight; - New_TotalUsedConn+=len; - } else { - // remove the candidate - if (j+1 < num_candidates) { - mysrvcCandidates[j] = mysrvcCandidates[num_candidates-1]; - } - j--; - num_candidates--; - } - } -*/ - - unsigned int New_sum=sum; - - if (New_sum==0) { - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC NULL because no backend ONLINE or with weight\n"); - if (l>32) { - free(mysrvcCandidates); - } -#ifdef TEST_AURORA - array_mysrvc_cands += num_candidates; -#endif // TEST_AURORA - return NULL; // if we reach here, we couldn't find any target - } - - // latency awareness algorithm is enabled only when compiled with USE_MYSRVC_ARRAY - if (sess && sess->thread->variables.min_num_servers_lantency_awareness) { - if ((int) num_candidates >= sess->thread->variables.min_num_servers_lantency_awareness) { - unsigned int servers_with_latency = 0; - unsigned int total_latency_us = 0; - // scan and verify that all servers have some latency - for (j=0; jcurrent_latency_us) { - servers_with_latency++; - total_latency_us += mysrvc->current_latency_us; - } - } - if (servers_with_latency == num_candidates) { - // all servers have some latency. - // That is good. If any server have no latency, something is wrong - // and we will skip this algorithm - sess->thread->status_variables.stvar[st_var_ConnPool_get_conn_latency_awareness]++; - unsigned int avg_latency_us = 0; - avg_latency_us = total_latency_us/num_candidates; - for (j=0; jcurrent_latency_us > avg_latency_us) { - // remove the candidate - if (j+1 < num_candidates) { - mysrvcCandidates[j] = mysrvcCandidates[num_candidates-1]; - } - j--; - num_candidates--; - } - } - // we scan again to adjust weight - New_sum = 0; - for (j=0; jweight; - } - } - } - } - - - unsigned int k; - if (New_sum > 32768) { - k=rand()%New_sum; - } else { - k=fastrand()%New_sum; - } - k++; - New_sum=0; - - for (j=0; jweight; - if (k<=New_sum) { - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC %p, server %s:%d\n", mysrvc, mysrvc->address, mysrvc->port); - if (l>32) { - free(mysrvcCandidates); - } -#ifdef TEST_AURORA - array_mysrvc_cands += num_candidates; -#endif // TEST_AURORA - return mysrvc; - } - } - } else { - time_t t = time(NULL); - - if (t - last_hg_log > 1) { - last_hg_log = time(NULL); - proxy_error("Hostgroup %u has no servers available!\n", hid); - } - } - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySrvC NULL\n"); - if (l>32) { - free(mysrvcCandidates); - } -#ifdef TEST_AURORA - array_mysrvc_cands += num_candidates; -#endif // TEST_AURORA - return NULL; // if we reach here, we couldn't find any target -} - -//unsigned int MySrvList::cnt() { -// return servers->len; -//} - -//MySrvC * MySrvList::idx(unsigned int i) { return (MySrvC *)servers->index(i); } - -void MySrvConnList::get_random_MyConn_inner_search(unsigned int start, unsigned int end, unsigned int& conn_found_idx, unsigned int& connection_quality_level, unsigned int& number_of_matching_session_variables, const MySQL_Connection * client_conn) { - char *schema = client_conn->userinfo->schemaname; - MySQL_Connection * conn=NULL; - unsigned int k; - for (k = start; k < end; k++) { - conn = (MySQL_Connection *)conns->index(k); - if (conn->match_tracked_options(client_conn)) { - if (connection_quality_level == 0) { - // this is our best candidate so far - connection_quality_level = 1; - conn_found_idx = k; - } - if (conn->requires_CHANGE_USER(client_conn)==false) { - if (connection_quality_level == 1) { - // this is our best candidate so far - connection_quality_level = 2; - conn_found_idx = k; - } - unsigned int cnt_match = 0; // number of matching session variables - unsigned int not_match = 0; // number of not matching session variables - cnt_match = conn->number_of_matching_session_variables(client_conn, not_match); - if (strcmp(conn->userinfo->schemaname,schema)==0) { - cnt_match++; - } else { - not_match++; - } - if (not_match==0) { - // it seems we found the perfect connection - number_of_matching_session_variables = cnt_match; - connection_quality_level = 3; - conn_found_idx = k; - return; // exit immediately, we found the perfect connection - } else { - // we didn't find the perfect connection - // but maybe is better than what we have so far? - if (cnt_match > number_of_matching_session_variables) { - // this is our best candidate so far - number_of_matching_session_variables = cnt_match; - conn_found_idx = k; - } - } - } else { - if (connection_quality_level == 1) { - int rca = mysql_thread___reset_connection_algorithm; - if (rca==1) { - int ql = GloMTH->variables.connpoll_reset_queue_length; - if (ql==0) { - // if: - // mysql-reset_connection_algorithm=1 and - // mysql-connpoll_reset_queue_length=0 - // we will not return a connection with connection_quality_level == 1 - // because we want to run COM_CHANGE_USER - // This change was introduced to work around Galera bug - // https://github.com/codership/galera/issues/613 - connection_quality_level = 0; - } - } - } - } - } - } -} - - - -MySQL_Connection * MySrvConnList::get_random_MyConn(MySQL_Session *sess, bool ff) { - MySQL_Connection * conn=NULL; - unsigned int i; - unsigned int conn_found_idx; - unsigned int l=conns_length(); - unsigned int connection_quality_level = 0; - bool needs_warming = false; - // connection_quality_level: - // 0 : not found any good connection, tracked options are not OK - // 1 : tracked options are OK , but CHANGE USER is required - // 2 : tracked options are OK , CHANGE USER is not required, but some SET statement or INIT_DB needs to be executed - // 3 : tracked options are OK , CHANGE USER is not required, and it seems that SET statements or INIT_DB ARE not required - unsigned int number_of_matching_session_variables = 0; // this includes session variables AND schema - bool connection_warming = mysql_thread___connection_warming; - int free_connections_pct = mysql_thread___free_connections_pct; - if (mysrvc->myhgc->attributes.configured == true) { - // mysql_hostgroup_attributes takes priority - connection_warming = mysrvc->myhgc->attributes.connection_warming; - free_connections_pct = mysrvc->myhgc->attributes.free_connections_pct; - } - if (connection_warming == true) { - unsigned int total_connections = mysrvc->ConnectionsFree->conns_length()+mysrvc->ConnectionsUsed->conns_length(); - unsigned int expected_warm_connections = free_connections_pct*mysrvc->max_connections/100; - if (total_connections < expected_warm_connections) { - needs_warming = true; - } - } - if (l && ff==false && needs_warming==false) { - if (l>32768) { - i=rand()%l; - } else { - i=fastrand()%l; - } - if (sess && sess->client_myds && sess->client_myds->myconn && sess->client_myds->myconn->userinfo) { - MySQL_Connection * client_conn = sess->client_myds->myconn; - get_random_MyConn_inner_search(i, l, conn_found_idx, connection_quality_level, number_of_matching_session_variables, client_conn); - if (connection_quality_level !=3 ) { // we didn't find the perfect connection - get_random_MyConn_inner_search(0, i, conn_found_idx, connection_quality_level, number_of_matching_session_variables, client_conn); - } - // connection_quality_level: - // 1 : tracked options are OK , but CHANGE USER is required - // 2 : tracked options are OK , CHANGE USER is not required, but some SET statement or INIT_DB needs to be executed - switch (connection_quality_level) { - case 0: // not found any good connection, tracked options are not OK - // we must check if connections need to be freed before - // creating a new connection - { - unsigned int conns_free = mysrvc->ConnectionsFree->conns_length(); - unsigned int conns_used = mysrvc->ConnectionsUsed->conns_length(); - unsigned int pct_max_connections = (3 * mysrvc->max_connections) / 4; - unsigned int connections_to_free = 0; - - if (conns_free >= 1) { - // connection cleanup is triggered when connections exceed 3/4 of the total - // allowed max connections, this cleanup ensures that at least *one connection* - // will be freed. - if (pct_max_connections <= (conns_free + conns_used)) { - connections_to_free = (conns_free + conns_used) - pct_max_connections; - if (connections_to_free == 0) connections_to_free = 1; - } - - while (conns_free && connections_to_free) { - MySQL_Connection* conn = mysrvc->ConnectionsFree->remove(0); - delete conn; - - conns_free = mysrvc->ConnectionsFree->conns_length(); - connections_to_free -= 1; - } - } - - // we must create a new connection - conn = new MySQL_Connection(); - conn->parent=mysrvc; - // if attributes.multiplex == true , STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG is set to false. And vice-versa - conn->set_status(!conn->parent->myhgc->attributes.multiplex, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); - __sync_fetch_and_add(&MyHGM->status.server_connections_created, 1); - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); - } - break; - case 1: //tracked options are OK , but CHANGE USER is required - // we may consider creating a new connection - { - unsigned int conns_free = mysrvc->ConnectionsFree->conns_length(); - unsigned int conns_used = mysrvc->ConnectionsUsed->conns_length(); - if ((conns_used > conns_free) && (mysrvc->max_connections > (conns_free/2 + conns_used/2)) ) { - conn = new MySQL_Connection(); - conn->parent=mysrvc; - // if attributes.multiplex == true , STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG is set to false. And vice-versa - conn->set_status(!conn->parent->myhgc->attributes.multiplex, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); - __sync_fetch_and_add(&MyHGM->status.server_connections_created, 1); - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); - } else { - conn=(MySQL_Connection *)conns->remove_index_fast(conn_found_idx); - } - } - break; - case 2: // tracked options are OK , CHANGE USER is not required, but some SET statement or INIT_DB needs to be executed - case 3: // tracked options are OK , CHANGE USER is not required, and it seems that SET statements or INIT_DB ARE not required - // here we return the best connection we have, no matter if connection_quality_level is 2 or 3 - conn=(MySQL_Connection *)conns->remove_index_fast(conn_found_idx); - break; - default: // this should never happen - // LCOV_EXCL_START - assert(0); - break; - // LCOV_EXCL_STOP - } - } else { - conn=(MySQL_Connection *)conns->remove_index_fast(i); - } - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); - return conn; - } else { - unsigned long long curtime = monotonic_time(); - curtime = curtime / 1000 / 1000; // convert to second - MyHGC *_myhgc = mysrvc->myhgc; - if (curtime > _myhgc->current_time_now) { - _myhgc->current_time_now = curtime; - _myhgc->new_connections_now = 0; - } - _myhgc->new_connections_now++; - unsigned int throttle_connections_per_sec_to_hostgroup = (unsigned int) mysql_thread___throttle_connections_per_sec_to_hostgroup; - if (_myhgc->attributes.configured == true) { - // mysql_hostgroup_attributes takes priority - throttle_connections_per_sec_to_hostgroup = _myhgc->attributes.throttle_connections_per_sec; - } - if (_myhgc->new_connections_now > (unsigned int) throttle_connections_per_sec_to_hostgroup) { - __sync_fetch_and_add(&MyHGM->status.server_connections_delayed, 1); - return NULL; - } else { - conn = new MySQL_Connection(); - conn->parent=mysrvc; - // if attributes.multiplex == true , STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG is set to false. And vice-versa - conn->set_status(!conn->parent->myhgc->attributes.multiplex, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); - __sync_fetch_and_add(&MyHGM->status.server_connections_created, 1); - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); - return conn; - } - } - return NULL; // never reach here -} - void MySQL_HostGroups_Manager::unshun_server_all_hostgroups(const char * address, uint16_t port, time_t t, int max_wait_sec, unsigned int *skip_hid) { // we scan all hostgroups looking for a specific server to unshun // if skip_hid is not NULL , the specific hostgroup is skipped @@ -3710,7 +2445,7 @@ void MySQL_HostGroups_Manager::unshun_server_all_hostgroups(const char * address bool found = false; // was this server already found in this hostgroup? for (j=0; found==false && j<(int)myhgc->mysrvs->cnt(); j++) { MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED) { // we only care for SHUNNED nodes // Note that we check for address and port only for status==MYSQL_SERVER_STATUS_SHUNNED , // that means that potentially we will pass by the matching node and still looping . @@ -3728,7 +2463,7 @@ void MySQL_HostGroups_Manager::unshun_server_all_hostgroups(const char * address if (GloMTH->variables.hostgroup_manager_verbose >= 3) { proxy_info("Unshunning server %d:%s:%d . time_last_detected_error=%lu\n", mysrvc->myhgc->hid, address, port, mysrvc->time_last_detected_error); } - mysrvc->status=MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); mysrvc->shunned_automatic=false; mysrvc->shunned_and_kill_all_connections=false; mysrvc->connect_ERR_at_time_last_detected_error=0; @@ -3803,7 +2538,7 @@ MySQL_Connection * MySQL_HostGroups_Manager::get_MyConn_from_pool(unsigned int _ void MySQL_HostGroups_Manager::destroy_MyConn_from_pool(MySQL_Connection *c, bool _lock) { bool to_del=true; // the default, legacy behavior MySrvC *mysrvc=(MySrvC *)c->parent; - if (mysrvc->status==MYSQL_SERVER_STATUS_ONLINE && c->send_quit && queue.size() < __sync_fetch_and_add(&GloMTH->variables.connpoll_reset_queue_length,0)) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE && c->send_quit && queue.size() < __sync_fetch_and_add(&GloMTH->variables.connpoll_reset_queue_length, 0)) { if (c->async_state_machine==ASYNC_IDLE) { // overall, the backend seems healthy and so it is the connection. Try to reset it int myerr=mysql_errno(c->mysql); @@ -3963,7 +2698,7 @@ void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, const MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); if (strcmp(mysrvc->address,address)==0 && mysrvc->port==port) { mysrvc->cur_replication_lag = current_replication_lag; - if (mysrvc->status==MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { if ( // (current_replication_lag==-1 ) // || @@ -3977,7 +2712,7 @@ void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, const mysrvc->cur_replication_lag_count += 1; if (mysrvc->cur_replication_lag_count >= (unsigned int)mysql_thread___monitor_replication_lag_count) { proxy_warning("Shunning server %s:%d from HG %u with replication lag of %d second, count number: '%d'\n", address, port, myhgc->hid, current_replication_lag, mysrvc->cur_replication_lag_count); - mysrvc->status=MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG; + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG); } else { proxy_info( "Not shunning server %s:%d from HG %u with replication lag of %d second, count number: '%d' < replication_lag_count: '%d'\n", @@ -3993,13 +2728,13 @@ void MySQL_HostGroups_Manager::replication_lag_action_inner(MyHGC *myhgc, const mysrvc->cur_replication_lag_count = 0; } } else { - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { if ( (current_replication_lag>=0 && ((unsigned int)current_replication_lag <= mysrvc->max_replication_lag)) || (current_replication_lag==-2) // see issue 959 ) { - mysrvc->status=MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); proxy_warning("Re-enabling server %s:%d from HG %u with replication lag of %d second\n", address, port, myhgc->hid, current_replication_lag); mysrvc->cur_replication_lag_count = 0; } @@ -4073,25 +2808,25 @@ void MySQL_HostGroups_Manager::group_replication_lag_action_set_server_status(My MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); proxy_debug( PROXY_DEBUG_MONITOR, 6, "Server 'MySrvC' - address: %s, port: %d, status: %d\n", mysrvc->address, - mysrvc->port, mysrvc->status + mysrvc->port, (int)mysrvc->get_status() ); if (strcmp(mysrvc->address,address)==0 && mysrvc->port==port) { if (enable == true) { - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG || mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED) { - mysrvc->status=MYSQL_SERVER_STATUS_ONLINE; + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG || mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED) { + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); proxy_info("Re-enabling server %u:%s:%d from replication lag\n", myhgc->hid, address, port); } } else { - if (mysrvc->status==MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status()==MYSQL_SERVER_STATUS_ONLINE) { proxy_warning("Shunning 'soft' server %u:%s:%d with replication lag, count number: %d\n", myhgc->hid, address, port, lag_count); - mysrvc->status=MYSQL_SERVER_STATUS_SHUNNED; + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED); } else { - if (mysrvc->status==MYSQL_SERVER_STATUS_SHUNNED) { + if (mysrvc->get_status()==MYSQL_SERVER_STATUS_SHUNNED) { if (lag_count >= ( mysql_thread___monitor_groupreplication_max_transactions_behind_count * 2 )) { proxy_warning("Shunning 'hard' server %u:%s:%d with replication lag, count number: %d\n", myhgc->hid, address, port, lag_count); - mysrvc->status=MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG; + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG); } } } @@ -4138,8 +2873,8 @@ void MySQL_HostGroups_Manager::group_replication_lag_action( MyHGC* myhgc = nullptr; if ( - mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only == 0 || - mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only == 2 || + mysql_thread___monitor_groupreplication_max_transactions_behind_for_read_only == 0 || + mysql_thread___monitor_groupreplication_max_transactions_behind_for_read_only == 2 || enable ) { if (read_only == false) { @@ -4149,8 +2884,8 @@ void MySQL_HostGroups_Manager::group_replication_lag_action( } if ( - mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only == 1 || - mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only == 2 || + mysql_thread___monitor_groupreplication_max_transactions_behind_for_read_only == 1 || + mysql_thread___monitor_groupreplication_max_transactions_behind_for_read_only == 2 || enable ) { myhgc = MyHGM->MyHGC_find(reader_hostgroup); @@ -4176,7 +2911,7 @@ void MySQL_HostGroups_Manager::drop_all_idle_connections() { MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); - if (mysrvc->status!=MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status()!=MYSQL_SERVER_STATUS_ONLINE) { proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d is not online\n", mysrvc->address, mysrvc->port); //__sync_fetch_and_sub(&status.server_connections_connected, mysrvc->ConnectionsFree->conns->len); mysrvc->ConnectionsFree->drop_all_connections(); @@ -4452,7 +3187,7 @@ SQLite3_result * MySQL_HostGroups_Manager::SQL3_Free_Connections() { MyHGC *myhgc=(MyHGC *)MyHostGroups->index(i); for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); - if (mysrvc->status!=MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status()!=MYSQL_SERVER_STATUS_ONLINE) { proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d is not online\n", mysrvc->address, mysrvc->port); mysrvc->ConnectionsFree->drop_all_connections(); } @@ -4651,7 +3386,7 @@ void MySQL_HostGroups_Manager::p_update_connection_pool() { // proxysql_connection_pool_status metric p_update_connection_pool_update_gauge(endpoint_id, common_labels, - status.p_connection_pool_status_map, mysrvc->status + 1, p_hg_dyn_gauge::connection_pool_status); + status.p_connection_pool_status_map, ((int)mysrvc->get_status()) + 1, p_hg_dyn_gauge::connection_pool_status); } } @@ -4710,7 +3445,7 @@ SQLite3_result * MySQL_HostGroups_Manager::SQL3_Connection_Pool(bool _reset, int for (j=0; j<(int)myhgc->mysrvs->cnt(); j++) { MySrvC *mysrvc=(MySrvC *)myhgc->mysrvs->servers->index(j); if (hid == NULL) { - if (mysrvc->status!=MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status()!=MYSQL_SERVER_STATUS_ONLINE) { proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Server %s:%d is not online\n", mysrvc->address, mysrvc->port); //__sync_fetch_and_sub(&status.server_connections_connected, mysrvc->ConnectionsFree->conns->len); mysrvc->ConnectionsFree->drop_all_connections(); @@ -4734,7 +3469,7 @@ SQLite3_result * MySQL_HostGroups_Manager::SQL3_Connection_Pool(bool _reset, int pta[1]=strdup(mysrvc->address); sprintf(buf,"%d", mysrvc->port); pta[2]=strdup(buf); - switch (mysrvc->status) { + switch ((int)mysrvc->get_status()) { case 0: pta[3]=strdup("ONLINE"); break; @@ -5322,16 +4057,16 @@ bool MySQL_HostGroups_Manager::shun_and_killall(char *hostname, int port) { for (j=0; jmysrvs->idx(j); if (mysrvc->port==port && strcmp(mysrvc->address,hostname)==0) { - switch (mysrvc->status) { + switch ((MySerStatus)mysrvc->get_status()) { case MYSQL_SERVER_STATUS_SHUNNED: if (mysrvc->shunned_automatic==false) { break; } case MYSQL_SERVER_STATUS_ONLINE: - if (mysrvc->status == MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { ret = true; } - mysrvc->status=MYSQL_SERVER_STATUS_SHUNNED; + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED); case MYSQL_SERVER_STATUS_OFFLINE_SOFT: mysrvc->shunned_automatic=true; mysrvc->shunned_and_kill_all_connections=true; @@ -6179,7 +4914,7 @@ void MySQL_HostGroups_Manager::update_group_replication_add_autodiscovered( // the servers to runtime. if (strcmp(mysrvc->address,_host.c_str())==0 && mysrvc->port==_port) { srv_found = true; - if (mysrvc->status == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { reset_hg_attrs_server_defaults(mysrvc); update_hg_attrs_server_defaults(mysrvc, mysrvc->myhgc); proxy_info( @@ -6187,7 +4922,7 @@ void MySQL_HostGroups_Manager::update_group_replication_add_autodiscovered( " hostgroup=%d, weight=%ld, max_connections=%ld, use_ssl=%d\n", _host.c_str(), _port, reader_hg, mysrvc->weight, mysrvc->max_connections, mysrvc->use_ssl ); - mysrvc->status = MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); srv_found_offline = true; } } @@ -7663,6 +6398,7 @@ void MySQL_HostGroups_Manager::generate_mysql_hostgroup_attributes_table() { } } + (*proxy_sqlite3_finalize)(statement); delete incoming_hostgroup_attributes; incoming_hostgroup_attributes=NULL; } @@ -7717,6 +6453,7 @@ void MySQL_HostGroups_Manager::generate_mysql_servers_ssl_params_table() { string MapKey = MSSP.getMapKey(rand_del); Servers_SSL_Params_map.emplace(MapKey, MSSP); } + (*proxy_sqlite3_finalize)(statement); delete incoming_mysql_servers_ssl_params; incoming_mysql_servers_ssl_params=NULL; } @@ -7857,22 +6594,22 @@ bool MySQL_HostGroups_Manager::aws_aurora_replication_lag_action(int _whid, int if (strcmp(mysrvc->address,address)==0 && mysrvc->port==port) { // we found the server if (enable==false) { - if (mysrvc->status == MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { if (verbose) { proxy_warning("Shunning server %s:%d from HG %u with replication lag of %f microseconds\n", address, port, myhgc->hid, current_replication_lag_ms); } - mysrvc->status = MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG; + mysrvc->set_status(MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG); } } else { - if (mysrvc->status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { if (verbose) { proxy_warning("Re-enabling server %s:%d from HG %u with replication lag of %f microseconds\n", address, port, myhgc->hid, current_replication_lag_ms); } - mysrvc->status = MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); } } mysrvc->aws_aurora_current_lag_us = current_replication_lag_ms * 1000; - if (mysrvc->status == MYSQL_SERVER_STATUS_ONLINE || mysrvc->status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE || mysrvc->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { // we perform check only if ONLINE or lagging if (ret) { if (_whid==(int)myhgc->hid && is_writer==false) { @@ -7901,8 +6638,8 @@ bool MySQL_HostGroups_Manager::aws_aurora_replication_lag_action(int _whid, int if (is_writer==true) if (enable==true) if (_whid==(int)myhgc->hid) - if (mysrvc->status == MYSQL_SERVER_STATUS_OFFLINE_HARD) { - mysrvc->status = MYSQL_SERVER_STATUS_ONLINE; + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); proxy_warning("Re-enabling server %s:%d from HG %u because it is a writer\n", address, port, myhgc->hid); ret = true; } @@ -7949,10 +6686,10 @@ int MySQL_HostGroups_Manager::create_new_server_in_hg( // 'servers_defaults' attributes from its corresponding 'MyHGC'. This way we ensure uniform behavior // of new servers, and 'OFFLINE_HARD' ones when a user update 'servers_defaults' values, and reloads // the servers to runtime. - if (mysrvc && mysrvc->status == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + if (mysrvc && mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { reset_hg_attrs_server_defaults(mysrvc); update_hg_attrs_server_defaults(mysrvc, mysrvc->myhgc); - mysrvc->status = MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); proxy_info( "Found healthy previously discovered %s node %s:%d as 'OFFLINE_HARD', setting back as 'ONLINE' with:" @@ -7984,7 +6721,7 @@ int MySQL_HostGroups_Manager::remove_server_in_hg(uint32_t hid, const string& ad ); // Set the server status - mysrvc->status=MYSQL_SERVER_STATUS_OFFLINE_HARD; + mysrvc->set_status(MYSQL_SERVER_STATUS_OFFLINE_HARD); mysrvc->ConnectionsFree->drop_all_connections(); // TODO-NOTE: This is only required in case the caller isn't going to perform: @@ -8453,11 +7190,11 @@ void MySQL_HostGroups_Manager::HostGroup_Server_Mapping::copy_if_not_exists(Type for (auto& node : append) { - if (node.srv->status == MYSQL_SERVER_STATUS_SHUNNED || - node.srv->status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { + if (node.srv->get_status() == MYSQL_SERVER_STATUS_SHUNNED || + node.srv->get_status() == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { // Status updated from "*SHUNNED" to "ONLINE" as "read_only" value was successfully // retrieved from the backend server, indicating server is now online. - node.srv->status = MYSQL_SERVER_STATUS_ONLINE; + node.srv->set_status(MYSQL_SERVER_STATUS_ONLINE); } MySrvC* new_srv = insert_HGM(get_hostgroup_id(dest_type, node), node.srv); @@ -8517,7 +7254,7 @@ MySrvC* MySQL_HostGroups_Manager::HostGroup_Server_Mapping::insert_HGM(unsigned for (uint32_t j = 0; j < myhgc->mysrvs->cnt(); j++) { MySrvC* mysrvc = static_cast(myhgc->mysrvs->servers->index(j)); if (strcmp(mysrvc->address, srv->address) == 0 && mysrvc->port == srv->port) { - if (mysrvc->status == MYSQL_SERVER_STATUS_OFFLINE_HARD) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_OFFLINE_HARD) { mysrvc->gtid_port = srv->gtid_port; mysrvc->weight = srv->weight; @@ -8527,7 +7264,7 @@ MySrvC* MySQL_HostGroups_Manager::HostGroup_Server_Mapping::insert_HGM(unsigned mysrvc->use_ssl = srv->use_ssl; mysrvc->max_latency_us = srv->max_latency_us; mysrvc->comment = strdup(srv->comment); - mysrvc->status = MYSQL_SERVER_STATUS_ONLINE; + mysrvc->set_status(MYSQL_SERVER_STATUS_ONLINE); if (GloMTH->variables.hostgroup_manager_verbose) { proxy_info( @@ -8547,12 +7284,12 @@ MySrvC* MySQL_HostGroups_Manager::HostGroup_Server_Mapping::insert_HGM(unsigned if (!ret_srv) { if (GloMTH->variables.hostgroup_manager_verbose) { - proxy_info("Creating new server in HG %d : %s:%d , gtid_port=%d, weight=%ld, status=%d\n", hostgroup_id, srv->address, srv->port, srv->gtid_port, srv->weight, srv->status); + proxy_info("Creating new server in HG %d : %s:%d , gtid_port=%d, weight=%ld, status=%d\n", hostgroup_id, srv->address, srv->port, srv->gtid_port, srv->weight, (int)srv->get_status()); } - proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Adding new server %s:%d , weight=%ld, status=%d, mem_ptr=%p into hostgroup=%d\n", srv->address, srv->port, srv->weight, srv->status, srv, hostgroup_id); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 5, "Adding new server %s:%d , weight=%ld, status=%d, mem_ptr=%p into hostgroup=%d\n", srv->address, srv->port, srv->weight, (int)srv->get_status(), srv, hostgroup_id); - ret_srv = new MySrvC(srv->address, srv->port, srv->gtid_port, srv->weight, srv->status, srv->compression, + ret_srv = new MySrvC(srv->address, srv->port, srv->gtid_port, srv->weight, srv->get_status(), srv->compression, srv->max_connections, srv->max_replication_lag, srv->use_ssl, (srv->max_latency_us / 1000), srv->comment); myhgc->mysrvs->add(ret_srv); @@ -8563,7 +7300,7 @@ MySrvC* MySQL_HostGroups_Manager::HostGroup_Server_Mapping::insert_HGM(unsigned void MySQL_HostGroups_Manager::HostGroup_Server_Mapping::remove_HGM(MySrvC* srv) { proxy_warning("Removed server at address %p, hostgroup %d, address %s port %d. Setting status OFFLINE HARD and immediately dropping all free connections. Used connections will be dropped when trying to use them\n", (void*)srv, srv->myhgc->hid, srv->address, srv->port); - srv->status = MYSQL_SERVER_STATUS_OFFLINE_HARD; + srv->set_status(MYSQL_SERVER_STATUS_OFFLINE_HARD); srv->ConnectionsFree->drop_all_connections(); } diff --git a/lib/MySQL_Monitor.cpp b/lib/MySQL_Monitor.cpp index a943f10a98..2d518109b0 100644 --- a/lib/MySQL_Monitor.cpp +++ b/lib/MySQL_Monitor.cpp @@ -4091,6 +4091,14 @@ void* monitor_GR_thread_HG(void *arg) { // 3. Delegate the async fetching + actions of 'MySQL_Monitor_State_Data' with conns on 'Monitor_Poll'. /////////////////////////////////////////////////////////////////////////////////////// + // NOTE: This is just a best effort to avoid invalid memory accesses during 'SHUTDOWN SLOW'. Since the + // previous section is 'time consuming', there are good changes that we can detect a shutdown before + // trying to perform the monitoring actions on the acquired 'mmsd'. This exact scenario and timing has + // been previously observed in the CI. + if (GloMyMon->shutdown) { + break; + } + // Handle 'mmsds' that failed to optain conns for (const unique_ptr& mmsd : fail_mmsds) { async_gr_mon_actions_handler(mmsd.get()); diff --git a/lib/MySQL_PreparedStatement.cpp b/lib/MySQL_PreparedStatement.cpp index 5a47e81119..1b27c6a6e9 100644 --- a/lib/MySQL_PreparedStatement.cpp +++ b/lib/MySQL_PreparedStatement.cpp @@ -738,53 +738,16 @@ MySQL_STMTs_local_v14::~MySQL_STMTs_local_v14() { GloMyStmt->ref_count_server(global_stmt_id, -1); } } -/* - for (std::map::iterator it = m.begin(); - it != m.end(); ++it) { - uint32_t stmt_id = it->first; - MYSQL_STMT *stmt = it->second; - if (stmt) { // is a server - if (stmt->mysql) { - stmt->mysql->stmts = - list_delete(stmt->mysql->stmts, &stmt->list); - } - // we do a hack here: we pretend there is no server associate - // the connection will be dropped anyway immediately after - stmt->mysql = NULL; - mysql_stmt_close(stmt); - GloMyStmt->ref_count(stmt_id, -1, true, false); - } else { // is a client - GloMyStmt->ref_count(stmt_id, -1, true, true); - } - } - m.erase(m.begin(), m.end()); -*/ } MySQL_STMT_Global_info *MySQL_STMT_Manager_v14::find_prepared_statement_by_hash( uint64_t hash) { - //uint64_t hash, bool lock) { // removed in 2.3 MySQL_STMT_Global_info *ret = NULL; // assume we do not find it -/* removed in 2.3 - if (lock) { - pthread_rwlock_wrlock(&rwlock_); - } -*/ auto s = map_stmt_hash_to_info.find(hash); if (s != map_stmt_hash_to_info.end()) { ret = s->second; - //__sync_fetch_and_add(&ret->ref_count_client,1); // increase reference - //count -// __sync_fetch_and_add(&find_prepared_statement_by_hash_calls, 1); -// __sync_fetch_and_add(&ret->ref_count_client, 1); } - -/* removed in 2.3 - if (lock) { - pthread_rwlock_unlock(&rwlock_); - } -*/ return ret; } @@ -808,15 +771,6 @@ MySQL_STMT_Global_info *MySQL_STMT_Manager_v14::find_prepared_statement_by_stmt_ uint32_t MySQL_STMTs_local_v14::generate_new_client_stmt_id(uint64_t global_statement_id) { uint32_t ret=0; -/* - //auto s2 = global_stmt_to_client_ids.find(global_statement_id); - std::pair::iterator, std::multimap::iterator> itret; - itret = global_stmt_to_client_ids.equal_range(global_statement_id); - for (std::multimap::iterator it=itret.first; it!=itret.second; ++it) { - ret = it->second; - return ret; - } -*/ if (free_client_ids.size()) { ret=free_client_ids.top(); free_client_ids.pop(); @@ -893,7 +847,6 @@ MySQL_STMT_Global_info *MySQL_STMT_Manager_v14::add_prepared_statement( } else { next_id = next_statement_id; next_statement_id++; - //__sync_fetch_and_add(&next_statement_id, 1); } //next_statement_id++; @@ -902,14 +855,7 @@ MySQL_STMT_Global_info *MySQL_STMT_Manager_v14::add_prepared_statement( // insert it in both maps map_stmt_id_to_info.insert(std::make_pair(a->statement_id, a)); map_stmt_hash_to_info.insert(std::make_pair(a->hash, a)); - // ret=a->statement_id; ret = a; - // next_statement_id++; // increment it - //__sync_fetch_and_add(&ret->ref_count_client,1); // increase reference - //count -// __sync_fetch_and_add(&ret->ref_count_client, -// 1); // increase reference count -// *is_new = true; __sync_add_and_fetch(&num_stmt_with_ref_client_count_zero,1); __sync_add_and_fetch(&num_stmt_with_ref_server_count_zero,1); } @@ -918,9 +864,6 @@ MySQL_STMT_Global_info *MySQL_STMT_Manager_v14::add_prepared_statement( } ret->ref_count_server++; statuses.s_total++; -// __sync_fetch_and_add(&add_prepared_statement_calls, 1); -// __sync_fetch_and_add(&ret->ref_count_server, -// 1); // increase reference count if (lock) { pthread_rwlock_unlock(&rwlock_); } @@ -1101,14 +1044,6 @@ SQLite3_result * MySQL_STMT_Manager_v14::get_prepared_statements_global_infos() pgs->free_row(pta); delete pgs; } -/* - for (std::unordered_map::iterator it=digest_umap.begin(); it!=digest_umap.end(); ++it) { - QP_query_digest_stats *qds=(QP_query_digest_stats *)it->second; - char **pta=qds->get_row(); - result->add_row(pta); - qds->free_row(pta); - } -*/ unlock(); return result; } diff --git a/lib/MySQL_Protocol.cpp b/lib/MySQL_Protocol.cpp index 527c87bc93..cba71c6a8a 100644 --- a/lib/MySQL_Protocol.cpp +++ b/lib/MySQL_Protocol.cpp @@ -26,8 +26,6 @@ extern ClickHouse_Authentication *GloClickHouseAuth; #undef max_allowed_packet #endif -//#define RESULTSET_BUFLEN 16300 - #ifndef CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA #define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA 0x00200000 #endif @@ -43,240 +41,7 @@ static const char *plugins[3] = { "caching_sha2_password", }; -#ifdef DEBUG -static void __dump_pkt(const char *func, unsigned char *_ptr, unsigned int len) { - - if (GloVars.global.gdbg==0) return; - if (GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_PROTOCOL].verbosity < 8 ) return; - unsigned int i; - fprintf(stderr,"DUMP %d bytes FROM %s\n", len, func); - for(i = 0; i < len; i++) { - if(isprint(_ptr[i])) fprintf(stderr,"%c", _ptr[i]); else fprintf(stderr,"."); - if (i>0 && (i%16==15 || i==len-1)) { - unsigned int j; - if (i%16!=15) { - j=15-i%16; - while (j--) fprintf(stderr," "); - } - fprintf(stderr," --- "); - for (j=(i==len-1 ? ((int)(i/16))*16 : i-15 ) ; j<=i; j++) { - fprintf(stderr,"%02x ", _ptr[j]); - } - fprintf(stderr,"\n"); - } - } - fprintf(stderr,"\n\n"); - - -} -#endif - -char *sha1_pass_hex(char *sha1_pass) { - if (sha1_pass==NULL) return NULL; - char *buff=(char *)malloc(SHA_DIGEST_LENGTH*2+2); - buff[0]='*'; - buff[SHA_DIGEST_LENGTH*2+1]='\0'; - int i; - uint8_t a = 0; - for (i=0;iseed1= (rand_st->seed1*3+rand_st->seed2) % rand_st->max_value; - rand_st->seed2= (rand_st->seed1+rand_st->seed2+33) % rand_st->max_value; - return (((double) rand_st->seed1) / rand_st->max_value_dbl); -} - -void proxy_create_random_string(char *_to, uint length, struct rand_struct *rand_st) { - unsigned char * to = (unsigned char *)_to; - int rc = 0; - uint i; - rc = RAND_bytes((unsigned char *)to,length); -#ifdef DEBUG - if (rc==1) { - // For code coverage (to test the following code and other function) - // in DEBUG mode we pretend that RAND_bytes() fails 1% of the time - if(rand()%100==0) { - rc=0; - } - } -#endif // DEBUG - if (rc!=1) { - for (i=0; i 127) { - *to -= 128; - } - if (*to == 0) { - *to = 'a'; - } - to++; - } - } - *to= '\0'; -} - -static inline int write_encoded_length(unsigned char *p, uint64_t val, uint8_t len, char prefix) { - if (len==1) { - *p=(char)val; - return 1; - } - *p=prefix; - p++; - memcpy(p,&val,len-1); - return len; -} - -static inline int write_encoded_length_and_string(unsigned char *p, uint64_t val, uint8_t len, char prefix, char *string) { - int l=write_encoded_length(p,val,len,prefix); - if (val) { - memcpy(p+l,string,val); - } - return l+val; -} - -void proxy_compute_sha1_hash_multi(uint8_t *digest, const char *buf1, int len1, const char *buf2, int len2) { - PROXY_TRACE(); - const EVP_MD *evp_digest = EVP_get_digestbyname("sha1"); - assert(evp_digest != NULL); - EVP_MD_CTX *ctx = EVP_MD_CTX_new(); - EVP_MD_CTX_init(ctx); - EVP_DigestInit_ex(ctx, evp_digest, NULL); - EVP_DigestUpdate(ctx, buf1, len1); - EVP_DigestUpdate(ctx, buf2, len2); - unsigned int olen = 0; - EVP_DigestFinal(ctx, digest, &olen); - EVP_MD_CTX_free(ctx); -} - -void proxy_compute_sha1_hash(uint8_t *digest, const char *buf, int len) { - PROXY_TRACE(); - const EVP_MD *evp_digest = EVP_get_digestbyname("sha1"); - assert(evp_digest != NULL); - EVP_MD_CTX *ctx = EVP_MD_CTX_new(); - EVP_MD_CTX_init(ctx); - EVP_DigestInit_ex(ctx, evp_digest, NULL); - EVP_DigestUpdate(ctx, buf, len); - unsigned int olen = 0; - EVP_DigestFinal(ctx, digest, &olen); - EVP_MD_CTX_free(ctx); -} - -void proxy_compute_two_stage_sha1_hash(const char *password, size_t pass_len, uint8_t *hash_stage1, uint8_t *hash_stage2) { - proxy_compute_sha1_hash(hash_stage1, password, pass_len); - proxy_compute_sha1_hash(hash_stage2, (const char *) hash_stage1, SHA_DIGEST_LENGTH); -} - -void proxy_my_crypt(char *to, const uint8_t *s1, const uint8_t *s2, uint len) { - const uint8_t *s1_end= s1 + len; - while (s1 < s1_end) - *to++= *s1++ ^ *s2++; -} - -unsigned char decode_char(char x) { - if (x >= '0' && x <= '9') - return (x - 0x30); - else if (x >= 'A' && x <= 'F') - return(x - 0x37); - else if (x >= 'a' && x <= 'f') - return(x - 0x57); - else { - proxy_error("Invalid char"); - return 0; - } -} - -void unhex_pass(uint8_t *out, const char *in) { - int i=0; - for (i=0;ibuffer + myrs->buffer_used; myrs->buffer_used += size; } - memcpy(_ptr, &myhdr, sizeof(mysql_hdr)); - int l=sizeof(mysql_hdr); + memcpy(_ptr, &myhdr, sizeof(mysql_hdr)); + int l=sizeof(mysql_hdr); _ptr[l]=0xfe; l++; int16_t internal_status = status; if (sess) { @@ -2977,501 +2725,6 @@ bool MySQL_Protocol::generate_COM_QUERY_from_COM_FIELD_LIST(PtrSize_t *pkt) { return true; } -MySQL_ResultSet::MySQL_ResultSet() { - buffer = NULL; - //reset_pid = true; -} - -void MySQL_ResultSet::buffer_init(MySQL_Protocol* myproto) { - if (buffer==NULL) { - buffer=(unsigned char *)malloc(RESULTSET_BUFLEN); - } - - buffer_used=0; - myprot = myproto; -} - -void MySQL_ResultSet::init(MySQL_Protocol *_myprot, MYSQL_RES *_res, MYSQL *_my, MYSQL_STMT *_stmt) { - PROXY_TRACE2(); - transfer_started=false; - resultset_completed=false; - myprot=_myprot; - mysql=_my; - stmt=_stmt; - if (buffer==NULL) { - //if (_stmt==NULL) { // we allocate this buffer only for not prepared statements - // removing the previous assumption. We allocate this buffer also for prepared statements - buffer=(unsigned char *)malloc(RESULTSET_BUFLEN); - //} - } - buffer_used=0; - myds=NULL; - if (myprot) { // if myprot = NULL , this is a mirror - myds=myprot->get_myds(); - } - //if (reset_pid==true) { - sid=0; - //PSarrayOUT = NULL; - if (myprot) { // if myprot = NULL , this is a mirror - sid=myds->pkt_sid+1; - //PSarrayOUT = new PtrSizeArray(8); - } - //} - //reset_pid=true; - result=_res; - resultset_size=0; - num_rows=0; - num_fields=mysql_field_count(mysql); - PtrSize_t pkt; - // immediately generate the first set of packets - // columns count - if (myprot==NULL) { - return; // this is a mirror - } - MySQL_Data_Stream * c_myds = *(myprot->myds); - if (c_myds->com_field_list==false) { - myprot->generate_pkt_column_count(false,&pkt.ptr,&pkt.size,sid,num_fields,this); - sid++; - resultset_size+=pkt.size; - } - // columns description - for (unsigned int i=0; icom_field_list==false) { - // we are replacing generate_pkt_field() with a more efficient version - //myprot->generate_pkt_field(false,&pkt.ptr,&pkt.size,sid,field->db,field->table,field->org_table,field->name,field->org_name,field->charsetnr,field->length,field->type,field->flags,field->decimals,false,0,NULL,this); - myprot->generate_pkt_field2(&pkt.ptr,&pkt.size,sid,field,this); - resultset_size+=pkt.size; - sid++; - } else { - if (c_myds->com_field_wild==NULL || mywildcmp(c_myds->com_field_wild,field->name)) { - myprot->generate_pkt_field(false,&pkt.ptr,&pkt.size,sid,field->db,field->table,field->org_table,field->name,field->org_name,field->charsetnr,field->length,field->type,field->flags,field->decimals,true,4,(char *)"null",this); - resultset_size+=pkt.size; - sid++; - } - } - } - - deprecate_eof_active = c_myds->myconn && (c_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF); - - // first EOF - unsigned int nTrx=myds->sess->NumActiveTransactions(); - uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); - if (myds->sess->autocommit) setStatus += SERVER_STATUS_AUTOCOMMIT; - setStatus |= ( mysql->server_status & ~SERVER_STATUS_AUTOCOMMIT ); // get flags from server_status but ignore autocommit - setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128 -// if (_stmt) { // binary protocol , we also assume we have ALL the resultset -// myprot->generate_pkt_EOF(false,&pkt.ptr,&pkt.size,sid,0,mysql->server_status|setStatus); -// sid++; -// PSarrayOUT.add(pkt.ptr,pkt.size); -// resultset_size+=pkt.size; - //} else { - if (RESULTSET_BUFLEN <= (buffer_used + 9)) { - buffer_to_PSarrayOut(); - } - if (!deprecate_eof_active && myds->com_field_list==false) { - // up to 2.2.0 we used to add an EOF here. - // due to bug #3547 we move the logic into add_eof() that can now handle also prepared statements - PROXY_TRACE2(); - // if the backend server has CLIENT_DEPRECATE_EOF enabled, and the client does not support - // CLIENT_DEPRECATE_EOF, warning_count will be excluded from the intermediate EOF packet - add_eof((mysql->server_capabilities & CLIENT_DEPRECATE_EOF)); - } -} - - -// due to bug #3547 , in case of an error we remove the EOF -// and replace it with an ERR -// note that EOF is added on a packet on its own, instead of using a buffer, -// so that can be removed using remove_last_eof() -void MySQL_ResultSet::remove_last_eof() { - PROXY_TRACE2(); - PtrSize_t pkt; - if (PSarrayOUT.len) { - unsigned int l = PSarrayOUT.len-1; - PtrSize_t * pktp = PSarrayOUT.index(l); - if (pktp->size == 9) { - PROXY_TRACE2(); - PSarrayOUT.remove_index(l,&pkt); - l_free(pkt.size, pkt.ptr); - sid--; - } - } -} - -void MySQL_ResultSet::init_with_stmt(MySQL_Connection *myconn) { - PROXY_TRACE2(); - assert(stmt); - MYSQL_STMT *_stmt = stmt; - MySQL_Data_Stream * c_myds = *(myprot->myds); - buffer_to_PSarrayOut(); - unsigned long long total_size=0; - MYSQL_ROWS *r=_stmt->result.data; - if (r) { - total_size+=r->length; - if (r->length > 0xFFFFFF) { - total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); - } - total_size+=sizeof(mysql_hdr); - while(r->next) { - r=r->next; - total_size+=r->length; - if (r->length > 0xFFFFFF) { - total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); - } - total_size+=sizeof(mysql_hdr); - } -#define MAXBUFFSTMT 12*1024*1024 // hardcoded to LESS *very important* than 16MB - if (total_size < MAXBUFFSTMT) { - PtrSize_t pkt; - pkt.size=total_size; - pkt.ptr=malloc(pkt.size); - total_size=0; - r=_stmt->result.data; - add_row2(r,(unsigned char *)pkt.ptr); - total_size+=r->length; - if (r->length > 0xFFFFFF) { - total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); - } - total_size+=sizeof(mysql_hdr); - while(r->next) { - r=r->next; - add_row2(r,(unsigned char *)pkt.ptr+total_size); - total_size+=r->length; - if (r->length > 0xFFFFFF) { - total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); - } - total_size+=sizeof(mysql_hdr); - } - PSarrayOUT.add(pkt.ptr,pkt.size); - if (resultset_size/0xFFFFFFF != ((resultset_size+pkt.size)/0xFFFFFFF)) { - // generate a heartbeat every 256MB - unsigned long long curtime=monotonic_time(); - c_myds->sess->thread->atomic_curtime=curtime; - } - resultset_size+=pkt.size; - } else { // this code fixes a bug: resultset larger than 4GB would cause a crash - unsigned long long tmp_pkt_size = 0; - r=_stmt->result.data; - MYSQL_ROWS * r2 = NULL; - while (r) { - if (r->length >= MAXBUFFSTMT) { - // we have a large row - // we will send just that - tmp_pkt_size = r->length; - if (r->length > 0xFFFFFF) { - tmp_pkt_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); - } - tmp_pkt_size += sizeof(mysql_hdr); - PtrSize_t pkt; - pkt.size=tmp_pkt_size; - pkt.ptr=malloc(pkt.size); - add_row2(r,(unsigned char *)pkt.ptr); - PSarrayOUT.add(pkt.ptr,pkt.size); - if (resultset_size/0xFFFFFFF != ((resultset_size+pkt.size)/0xFFFFFFF)) { - // generate a heartbeat every 256MB - unsigned long long curtime=monotonic_time(); - c_myds->sess->thread->atomic_curtime=curtime; - } - resultset_size+=pkt.size; - r=r->next; // next row - } else { // we have small row - r2 = r; - tmp_pkt_size = 0; - unsigned int a = 0; - while (r && (tmp_pkt_size + r->length) < MAXBUFFSTMT) { - a++; - tmp_pkt_size += r->length; - tmp_pkt_size += sizeof(mysql_hdr); - //if (r->next) { - r = r->next; - //} - } - r = r2; // we reset it back to the beginning - if (tmp_pkt_size) { // this should always be true - unsigned long long tmp2 = 0; - PtrSize_t pkt; - pkt.size=tmp_pkt_size; - pkt.ptr=malloc(pkt.size); - while (tmp2 < tmp_pkt_size) { - add_row2(r,(unsigned char *)pkt.ptr+tmp2); - tmp2 += r->length; - tmp2 += sizeof(mysql_hdr); - r = r->next; - } - PSarrayOUT.add(pkt.ptr,pkt.size); - if (resultset_size/0xFFFFFFF != ((resultset_size+pkt.size)/0xFFFFFFF)) { - // generate a heartbeat every 256MB - unsigned long long curtime=monotonic_time(); - c_myds->sess->thread->atomic_curtime=curtime; - } - resultset_size+=pkt.size; - } - } - } - } - } - // up to 2.2.0 we were always adding an EOF - // due to bug #3547 , in case of an error we remove the EOF - // and replace it with an ERR - // note that EOF is added on a packet on its own, instead of using a buffer, - // so that can be removed - // - // NOTE: After 2.4.5 previous behavior is modified in favor of the following: - // - // When CLIENT_DEPRECATE_EOF two EOF packets are two be expected in the response: - // 1. After the columns definitions (This is added directly by 'MySQL_ResultSet::init'). - // 2. After the rows values, this can either be and EOF packet or a ERR packet in case of error. - // - // First EOF packet isn't optional, and it's just the second the one that is optionaly either an EOF - // or an ERR packet. The following code adds either the final EOF or ERR packet. This is equally valid - // for when CLIENT_DEPRECATE_EOF is enabled or not. If CLIENT_DEPRECATE_EOF is: - // * DISABLED: The behavior is as described before. - // * ENABLED: Code is identical for this case. The initial EOF packet is conditionally added by - // 'MySQL_ResultSet::init', thus, this packet should not be present if not needed at this point. - // In case of error an ERR packet needs to be added, otherwise `add_eof` handles the generation of - // the equivalent OK packet replacing the final EOF packet. - int myerr = mysql_stmt_errno(_stmt); - if (myerr) { - PROXY_TRACE2(); - add_err(myconn->myds); - } else { - PROXY_TRACE2(); - add_eof(); - } -} - -MySQL_ResultSet::~MySQL_ResultSet() { - PtrSize_t pkt; - //if (PSarrayOUT) { - while (PSarrayOUT.len) { - PSarrayOUT.remove_index_fast(0,&pkt); - l_free(pkt.size, pkt.ptr); - } - //delete PSarrayOUT; - //} - if (buffer) { - free(buffer); - buffer=NULL; - } - //if (myds) myds->pkt_sid=sid-1; -} - -// this function is used for binary protocol -// maybe later on can be adapted for text protocol too -unsigned int MySQL_ResultSet::add_row(MYSQL_ROWS *rows) { - unsigned int pkt_length=0; - MYSQL_ROW row = rows->data; - unsigned long row_length = rows->length; - // we call generate_pkt_row3 passing row_length - sid=myprot->generate_pkt_row3(this, &pkt_length, sid, 0, NULL, row, row_length); - sid++; - resultset_size+=pkt_length; - num_rows++; - return pkt_length; -} - - -// this function is used for text protocol -unsigned int MySQL_ResultSet::add_row(MYSQL_ROW row) { - unsigned long *lengths=mysql_fetch_lengths(result); - unsigned int pkt_length=0; - if (myprot) { - // we call generate_pkt_row3 without passing row_length - sid=myprot->generate_pkt_row3(this, &pkt_length, sid, num_fields, lengths, row, 0); - } else { - unsigned int col=0; - for (col=0; collength; - num_rows++; - uint8_t pkt_sid=sid; - if (length < (0xFFFFFF+sizeof(mysql_hdr))) { - mysql_hdr myhdr; - myhdr.pkt_length=length; - myhdr.pkt_id=pkt_sid; - memcpy(offset, &myhdr, sizeof(mysql_hdr)); - memcpy(offset+sizeof(mysql_hdr), row->data, row->length); - pkt_sid++; - } else { - unsigned int left=length; - unsigned int copied=0; - while (left>=0xFFFFFF) { - mysql_hdr myhdr; - myhdr.pkt_length=0xFFFFFF; - myhdr.pkt_id=pkt_sid; - pkt_sid++; - memcpy(offset, &myhdr, sizeof(mysql_hdr)); - offset+=sizeof(mysql_hdr); - char *o = (char *) row->data; - o += copied; - memcpy(offset, o, myhdr.pkt_length); - offset+=0xFFFFFF; - // we are writing a large packet (over 16MB), we assume we are always outside the buffer - copied+=0xFFFFFF; - left-=0xFFFFFF; - } - mysql_hdr myhdr; - myhdr.pkt_length=left; - myhdr.pkt_id=pkt_sid; - pkt_sid++; - memcpy(offset, &myhdr, sizeof(mysql_hdr)); - offset+=sizeof(mysql_hdr); - char *o = (char *) row->data; - o += copied; - memcpy(offset, o, myhdr.pkt_length); - // we are writing a large packet (over 16MB), we assume we are always outside the buffer - } - sid=pkt_sid; - return length; -} - -void MySQL_ResultSet::add_eof(bool suppress_warning_count) { - if (myprot) { - unsigned int nTrx=myds->sess->NumActiveTransactions(); - uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); - if (myds->sess->autocommit) setStatus += SERVER_STATUS_AUTOCOMMIT; - setStatus |= ( mysql->server_status & ~SERVER_STATUS_AUTOCOMMIT ); // get flags from server_status but ignore autocommit - setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128 - //myprot->generate_pkt_EOF(false,&pkt.ptr,&pkt.size,sid,0,mysql->server_status|setStatus); - //PSarrayOUT->add(pkt.ptr,pkt.size); - //sid++; - //resultset_size+=pkt.size; - - // Note: warnings count will only be sent to the client if mysql-query_digests is enabled - const MySQL_Backend* _mybe = myds->sess->mybe; - const MySQL_Data_Stream* _server_myds = (_mybe && _mybe->server_myds) ? _mybe->server_myds : nullptr; - const MySQL_Connection* _myconn = (_server_myds && _server_myds->myds_type == MYDS_BACKEND && _server_myds->myconn) ? - _server_myds->myconn : nullptr; - const unsigned int warning_count = (_myconn && suppress_warning_count == false) ? _myconn->warning_count : 0; - if (deprecate_eof_active) { - PtrSize_t pkt; - buffer_to_PSarrayOut(); - myprot->generate_pkt_OK(false, &pkt.ptr, &pkt.size, sid, 0, 0, setStatus, warning_count, NULL, true); - PSarrayOUT.add(pkt.ptr, pkt.size); - resultset_size += pkt.size; - } - else { - // due to bug #3547 , in case of an error we remove the EOF - // and replace it with an ERR - // note that EOF is added on a packet on its own, instead of using a buffer, - // so that can be removed using remove_last_eof() - buffer_to_PSarrayOut(); - myprot->generate_pkt_EOF(false, NULL, NULL, sid, warning_count, setStatus, this); - resultset_size += 9; - buffer_to_PSarrayOut(); - } - sid++; - } - resultset_completed=true; -} - -void MySQL_ResultSet::add_err(MySQL_Data_Stream *_myds) { - PtrSize_t pkt; - if (myprot) { - MYSQL *_mysql=_myds->myconn->mysql; - buffer_to_PSarrayOut(); - char sqlstate[10]; - sprintf(sqlstate,"%s",mysql_sqlstate(_mysql)); - if (_myds && _myds->killed_at) { // see case #750 - if (_myds->kill_type == 0) { - myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,1907,sqlstate,(char *)"Query execution was interrupted, query_timeout exceeded"); - MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1907); - } else { - myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,1317,sqlstate,(char *)"Query execution was interrupted"); - MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1317); - } - } else { - int myerr = 0; - // the error code is returned from: - // - mysql_stmt_errno() if using a prepared statement - // - mysql_errno() if not using a prepared statement - if (stmt) { - myerr = mysql_stmt_errno(stmt); - myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,myerr,sqlstate,mysql_stmt_error(stmt)); - } else { - myerr = mysql_errno(_mysql); - myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,myerr,sqlstate,mysql_error(_mysql)); - } - // TODO: Check this is a mysql error - MyHGM->p_update_mysql_error_counter(p_mysql_error_type::mysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, myerr); - } - PSarrayOUT.add(pkt.ptr,pkt.size); - sid++; - resultset_size+=pkt.size; - } - resultset_completed=true; -} - -/* -bool MySQL_ResultSet::get_COM_FIELD_LIST_response(PtrSizeArray *PSarrayFinal) { - transfer_started=true; - if (myprot) { - } - return resultset_completed; -} -*/ - -bool MySQL_ResultSet::get_resultset(PtrSizeArray *PSarrayFinal) { - transfer_started=true; - if (myprot) { - PSarrayFinal->copy_add(&PSarrayOUT,0,PSarrayOUT.len); - while (PSarrayOUT.len) - PSarrayOUT.remove_index(PSarrayOUT.len-1,NULL); - } - return resultset_completed; -} - -void MySQL_ResultSet::buffer_to_PSarrayOut(bool _last) { - if (buffer_used==0) - return; // exit immediately if the buffer is empty - if (buffer_used < RESULTSET_BUFLEN/2) { - if (_last == false) { - buffer=(unsigned char *)realloc(buffer,buffer_used); - } - } - PSarrayOUT.add(buffer,buffer_used); - if (_last) { - buffer = NULL; - } else { - buffer=(unsigned char *)malloc(RESULTSET_BUFLEN); - } - buffer_used=0; -} - -unsigned long long MySQL_ResultSet::current_size() { - unsigned long long intsize=0; - intsize+=sizeof(MySQL_ResultSet); - intsize+=RESULTSET_BUFLEN; // size of buffer - if (PSarrayOUT.len==0) // see bug #699 - return intsize; - intsize+=sizeof(PtrSizeArray); - intsize+=(PSarrayOUT.size*sizeof(PtrSize_t *)); - unsigned int i; - for (i=0; isize>RESULTSET_BUFLEN) { - intsize+=pkt->size; - } else { - intsize+=RESULTSET_BUFLEN; - } - } - return intsize; -} - my_bool proxy_mysql_stmt_close(MYSQL_STMT* stmt) { // Clean internal structures for 'stmt->mysql->stmts'. if (stmt->mysql) { diff --git a/lib/MySQL_ResultSet.cpp b/lib/MySQL_ResultSet.cpp new file mode 100644 index 0000000000..87e713aa76 --- /dev/null +++ b/lib/MySQL_ResultSet.cpp @@ -0,0 +1,549 @@ +#include +#include "proxysql.h" +#include "cpp.h" +#include "re2/re2.h" +#include "re2/regexp.h" + +#include "MySQL_PreparedStatement.h" +#include "MySQL_Data_Stream.h" +#include "MySQL_Authentication.hpp" +#include "MySQL_LDAP_Authentication.hpp" +#include "MySQL_Variables.h" + +#include + +//#include + +extern MySQL_Authentication *GloMyAuth; +extern MySQL_LDAP_Authentication *GloMyLdapAuth; +extern MySQL_Threads_Handler *GloMTH; + +#ifdef PROXYSQLCLICKHOUSE +extern ClickHouse_Authentication *GloClickHouseAuth; +#endif /* PROXYSQLCLICKHOUSE */ + +#ifdef max_allowed_packet +#undef max_allowed_packet +#endif + +#ifndef CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA +#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA 0x00200000 +#endif + +#include "proxysql_find_charset.h" + + +extern "C" char * sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen); + + +uint8_t mysql_encode_length(uint64_t len, char *hd); + + +MySQL_ResultSet::MySQL_ResultSet() { + buffer = NULL; + //reset_pid = true; +} + +void MySQL_ResultSet::buffer_init(MySQL_Protocol* myproto) { + if (buffer==NULL) { + buffer=(unsigned char *)malloc(RESULTSET_BUFLEN); + } + + buffer_used=0; + myprot = myproto; +} + +void MySQL_ResultSet::init(MySQL_Protocol *_myprot, MYSQL_RES *_res, MYSQL *_my, MYSQL_STMT *_stmt) { + PROXY_TRACE2(); + transfer_started=false; + resultset_completed=false; + myprot=_myprot; + mysql=_my; + stmt=_stmt; + if (buffer==NULL) { + //if (_stmt==NULL) { // we allocate this buffer only for not prepared statements + // removing the previous assumption. We allocate this buffer also for prepared statements + buffer=(unsigned char *)malloc(RESULTSET_BUFLEN); + //} + } + buffer_used=0; + myds=NULL; + if (myprot) { // if myprot = NULL , this is a mirror + myds=myprot->get_myds(); + } + //if (reset_pid==true) { + sid=0; + //PSarrayOUT = NULL; + if (myprot) { // if myprot = NULL , this is a mirror + sid=myds->pkt_sid+1; + //PSarrayOUT = new PtrSizeArray(8); + } + //} + //reset_pid=true; + result=_res; + resultset_size=0; + num_rows=0; + num_fields=mysql_field_count(mysql); + PtrSize_t pkt; + // immediately generate the first set of packets + // columns count + if (myprot==NULL) { + return; // this is a mirror + } + MySQL_Data_Stream * c_myds = *(myprot->myds); + if (c_myds->com_field_list==false) { + myprot->generate_pkt_column_count(false,&pkt.ptr,&pkt.size,sid,num_fields,this); + sid++; + resultset_size+=pkt.size; + } + // columns description + for (unsigned int i=0; icom_field_list==false) { + // we are replacing generate_pkt_field() with a more efficient version + //myprot->generate_pkt_field(false,&pkt.ptr,&pkt.size,sid,field->db,field->table,field->org_table,field->name,field->org_name,field->charsetnr,field->length,field->type,field->flags,field->decimals,false,0,NULL,this); + myprot->generate_pkt_field2(&pkt.ptr,&pkt.size,sid,field,this); + resultset_size+=pkt.size; + sid++; + } else { + if (c_myds->com_field_wild==NULL || mywildcmp(c_myds->com_field_wild,field->name)) { + myprot->generate_pkt_field(false,&pkt.ptr,&pkt.size,sid,field->db,field->table,field->org_table,field->name,field->org_name,field->charsetnr,field->length,field->type,field->flags,field->decimals,true,4,(char *)"null",this); + resultset_size+=pkt.size; + sid++; + } + } + } + + deprecate_eof_active = c_myds->myconn && (c_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF); + + // first EOF + unsigned int nTrx=myds->sess->NumActiveTransactions(); + uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); + if (myds->sess->autocommit) setStatus += SERVER_STATUS_AUTOCOMMIT; + setStatus |= ( mysql->server_status & ~SERVER_STATUS_AUTOCOMMIT ); // get flags from server_status but ignore autocommit + setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128 +// if (_stmt) { // binary protocol , we also assume we have ALL the resultset +// myprot->generate_pkt_EOF(false,&pkt.ptr,&pkt.size,sid,0,mysql->server_status|setStatus); +// sid++; +// PSarrayOUT.add(pkt.ptr,pkt.size); +// resultset_size+=pkt.size; + //} else { + if (RESULTSET_BUFLEN <= (buffer_used + 9)) { + buffer_to_PSarrayOut(); + } + if (!deprecate_eof_active && myds->com_field_list==false) { + // up to 2.2.0 we used to add an EOF here. + // due to bug #3547 we move the logic into add_eof() that can now handle also prepared statements + PROXY_TRACE2(); + // if the backend server has CLIENT_DEPRECATE_EOF enabled, and the client does not support + // CLIENT_DEPRECATE_EOF, warning_count will be excluded from the intermediate EOF packet + add_eof((mysql->server_capabilities & CLIENT_DEPRECATE_EOF)); + } +} + + +// due to bug #3547 , in case of an error we remove the EOF +// and replace it with an ERR +// note that EOF is added on a packet on its own, instead of using a buffer, +// so that can be removed using remove_last_eof() +void MySQL_ResultSet::remove_last_eof() { + PROXY_TRACE2(); + PtrSize_t pkt; + if (PSarrayOUT.len) { + unsigned int l = PSarrayOUT.len-1; + PtrSize_t * pktp = PSarrayOUT.index(l); + if (pktp->size == 9) { + PROXY_TRACE2(); + PSarrayOUT.remove_index(l,&pkt); + l_free(pkt.size, pkt.ptr); + sid--; + } + } +} + +void MySQL_ResultSet::init_with_stmt(MySQL_Connection *myconn) { + PROXY_TRACE2(); + assert(stmt); + MYSQL_STMT *_stmt = stmt; + MySQL_Data_Stream * c_myds = *(myprot->myds); + buffer_to_PSarrayOut(); + unsigned long long total_size=0; + MYSQL_ROWS *r=_stmt->result.data; + if (r) { + total_size+=r->length; + if (r->length > 0xFFFFFF) { + total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); + } + total_size+=sizeof(mysql_hdr); + while(r->next) { + r=r->next; + total_size+=r->length; + if (r->length > 0xFFFFFF) { + total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); + } + total_size+=sizeof(mysql_hdr); + } +#define MAXBUFFSTMT 12*1024*1024 // hardcoded to LESS *very important* than 16MB + if (total_size < MAXBUFFSTMT) { + PtrSize_t pkt; + pkt.size=total_size; + pkt.ptr=malloc(pkt.size); + total_size=0; + r=_stmt->result.data; + add_row2(r,(unsigned char *)pkt.ptr); + total_size+=r->length; + if (r->length > 0xFFFFFF) { + total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); + } + total_size+=sizeof(mysql_hdr); + while(r->next) { + r=r->next; + add_row2(r,(unsigned char *)pkt.ptr+total_size); + total_size+=r->length; + if (r->length > 0xFFFFFF) { + total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); + } + total_size+=sizeof(mysql_hdr); + } + PSarrayOUT.add(pkt.ptr,pkt.size); + if (resultset_size/0xFFFFFFF != ((resultset_size+pkt.size)/0xFFFFFFF)) { + // generate a heartbeat every 256MB + unsigned long long curtime=monotonic_time(); + c_myds->sess->thread->atomic_curtime=curtime; + } + resultset_size+=pkt.size; + } else { // this code fixes a bug: resultset larger than 4GB would cause a crash + unsigned long long tmp_pkt_size = 0; + r=_stmt->result.data; + MYSQL_ROWS * r2 = NULL; + while (r) { + if (r->length >= MAXBUFFSTMT) { + // we have a large row + // we will send just that + tmp_pkt_size = r->length; + if (r->length > 0xFFFFFF) { + tmp_pkt_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr); + } + tmp_pkt_size += sizeof(mysql_hdr); + PtrSize_t pkt; + pkt.size=tmp_pkt_size; + pkt.ptr=malloc(pkt.size); + add_row2(r,(unsigned char *)pkt.ptr); + PSarrayOUT.add(pkt.ptr,pkt.size); + if (resultset_size/0xFFFFFFF != ((resultset_size+pkt.size)/0xFFFFFFF)) { + // generate a heartbeat every 256MB + unsigned long long curtime=monotonic_time(); + c_myds->sess->thread->atomic_curtime=curtime; + } + resultset_size+=pkt.size; + r=r->next; // next row + } else { // we have small row + r2 = r; + tmp_pkt_size = 0; + unsigned int a = 0; + while (r && (tmp_pkt_size + r->length) < MAXBUFFSTMT) { + a++; + tmp_pkt_size += r->length; + tmp_pkt_size += sizeof(mysql_hdr); + //if (r->next) { + r = r->next; + //} + } + r = r2; // we reset it back to the beginning + if (tmp_pkt_size) { // this should always be true + unsigned long long tmp2 = 0; + PtrSize_t pkt; + pkt.size=tmp_pkt_size; + pkt.ptr=malloc(pkt.size); + while (tmp2 < tmp_pkt_size) { + add_row2(r,(unsigned char *)pkt.ptr+tmp2); + tmp2 += r->length; + tmp2 += sizeof(mysql_hdr); + r = r->next; + } + PSarrayOUT.add(pkt.ptr,pkt.size); + if (resultset_size/0xFFFFFFF != ((resultset_size+pkt.size)/0xFFFFFFF)) { + // generate a heartbeat every 256MB + unsigned long long curtime=monotonic_time(); + c_myds->sess->thread->atomic_curtime=curtime; + } + resultset_size+=pkt.size; + } + } + } + } + } + // up to 2.2.0 we were always adding an EOF + // due to bug #3547 , in case of an error we remove the EOF + // and replace it with an ERR + // note that EOF is added on a packet on its own, instead of using a buffer, + // so that can be removed + // + // NOTE: After 2.4.5 previous behavior is modified in favor of the following: + // + // When CLIENT_DEPRECATE_EOF two EOF packets are two be expected in the response: + // 1. After the columns definitions (This is added directly by 'MySQL_ResultSet::init'). + // 2. After the rows values, this can either be and EOF packet or a ERR packet in case of error. + // + // First EOF packet isn't optional, and it's just the second the one that is optionaly either an EOF + // or an ERR packet. The following code adds either the final EOF or ERR packet. This is equally valid + // for when CLIENT_DEPRECATE_EOF is enabled or not. If CLIENT_DEPRECATE_EOF is: + // * DISABLED: The behavior is as described before. + // * ENABLED: Code is identical for this case. The initial EOF packet is conditionally added by + // 'MySQL_ResultSet::init', thus, this packet should not be present if not needed at this point. + // In case of error an ERR packet needs to be added, otherwise `add_eof` handles the generation of + // the equivalent OK packet replacing the final EOF packet. + int myerr = mysql_stmt_errno(_stmt); + if (myerr) { + PROXY_TRACE2(); + add_err(myconn->myds); + } else { + PROXY_TRACE2(); + add_eof(); + } +} + +MySQL_ResultSet::~MySQL_ResultSet() { + PtrSize_t pkt; + //if (PSarrayOUT) { + while (PSarrayOUT.len) { + PSarrayOUT.remove_index_fast(0,&pkt); + l_free(pkt.size, pkt.ptr); + } + //delete PSarrayOUT; + //} + if (buffer) { + free(buffer); + buffer=NULL; + } + //if (myds) myds->pkt_sid=sid-1; +} + +// this function is used for binary protocol +// maybe later on can be adapted for text protocol too +unsigned int MySQL_ResultSet::add_row(MYSQL_ROWS *rows) { + unsigned int pkt_length=0; + MYSQL_ROW row = rows->data; + unsigned long row_length = rows->length; + // we call generate_pkt_row3 passing row_length + sid=myprot->generate_pkt_row3(this, &pkt_length, sid, 0, NULL, row, row_length); + sid++; + resultset_size+=pkt_length; + num_rows++; + return pkt_length; +} + + +// this function is used for text protocol +unsigned int MySQL_ResultSet::add_row(MYSQL_ROW row) { + unsigned long *lengths=mysql_fetch_lengths(result); + unsigned int pkt_length=0; + if (myprot) { + // we call generate_pkt_row3 without passing row_length + sid=myprot->generate_pkt_row3(this, &pkt_length, sid, num_fields, lengths, row, 0); + } else { + unsigned int col=0; + for (col=0; collength; + num_rows++; + uint8_t pkt_sid=sid; + if (length < (0xFFFFFF+sizeof(mysql_hdr))) { + mysql_hdr myhdr; + myhdr.pkt_length=length; + myhdr.pkt_id=pkt_sid; + memcpy(offset, &myhdr, sizeof(mysql_hdr)); + memcpy(offset+sizeof(mysql_hdr), row->data, row->length); + pkt_sid++; + } else { + unsigned int left=length; + unsigned int copied=0; + while (left>=0xFFFFFF) { + mysql_hdr myhdr; + myhdr.pkt_length=0xFFFFFF; + myhdr.pkt_id=pkt_sid; + pkt_sid++; + memcpy(offset, &myhdr, sizeof(mysql_hdr)); + offset+=sizeof(mysql_hdr); + char *o = (char *) row->data; + o += copied; + memcpy(offset, o, myhdr.pkt_length); + offset+=0xFFFFFF; + // we are writing a large packet (over 16MB), we assume we are always outside the buffer + copied+=0xFFFFFF; + left-=0xFFFFFF; + } + mysql_hdr myhdr; + myhdr.pkt_length=left; + myhdr.pkt_id=pkt_sid; + pkt_sid++; + memcpy(offset, &myhdr, sizeof(mysql_hdr)); + offset+=sizeof(mysql_hdr); + char *o = (char *) row->data; + o += copied; + memcpy(offset, o, myhdr.pkt_length); + // we are writing a large packet (over 16MB), we assume we are always outside the buffer + } + sid=pkt_sid; + return length; +} + +void MySQL_ResultSet::add_eof(bool suppress_warning_count) { + if (myprot) { + unsigned int nTrx=myds->sess->NumActiveTransactions(); + uint16_t setStatus = (nTrx ? SERVER_STATUS_IN_TRANS : 0 ); + if (myds->sess->autocommit) setStatus += SERVER_STATUS_AUTOCOMMIT; + setStatus |= ( mysql->server_status & ~SERVER_STATUS_AUTOCOMMIT ); // get flags from server_status but ignore autocommit + setStatus = setStatus & ~SERVER_STATUS_CURSOR_EXISTS; // Do not send cursor #1128 + //myprot->generate_pkt_EOF(false,&pkt.ptr,&pkt.size,sid,0,mysql->server_status|setStatus); + //PSarrayOUT->add(pkt.ptr,pkt.size); + //sid++; + //resultset_size+=pkt.size; + + // Note: warnings count will only be sent to the client if mysql-query_digests is enabled + const MySQL_Backend* _mybe = myds->sess->mybe; + const MySQL_Data_Stream* _server_myds = (_mybe && _mybe->server_myds) ? _mybe->server_myds : nullptr; + const MySQL_Connection* _myconn = (_server_myds && _server_myds->myds_type == MYDS_BACKEND && _server_myds->myconn) ? + _server_myds->myconn : nullptr; + const unsigned int warning_count = (_myconn && suppress_warning_count == false) ? _myconn->warning_count : 0; + if (deprecate_eof_active) { + PtrSize_t pkt; + buffer_to_PSarrayOut(); + myprot->generate_pkt_OK(false, &pkt.ptr, &pkt.size, sid, 0, 0, setStatus, warning_count, NULL, true); + PSarrayOUT.add(pkt.ptr, pkt.size); + resultset_size += pkt.size; + } + else { + // due to bug #3547 , in case of an error we remove the EOF + // and replace it with an ERR + // note that EOF is added on a packet on its own, instead of using a buffer, + // so that can be removed using remove_last_eof() + buffer_to_PSarrayOut(); + myprot->generate_pkt_EOF(false, NULL, NULL, sid, warning_count, setStatus, this); + resultset_size += 9; + buffer_to_PSarrayOut(); + } + sid++; + } + resultset_completed=true; +} + +void MySQL_ResultSet::add_err(MySQL_Data_Stream *_myds) { + PtrSize_t pkt; + if (myprot) { + MYSQL *_mysql=_myds->myconn->mysql; + buffer_to_PSarrayOut(); + char sqlstate[10]; + sprintf(sqlstate,"%s",mysql_sqlstate(_mysql)); + if (_myds && _myds->killed_at) { // see case #750 + if (_myds->kill_type == 0) { + myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,1907,sqlstate,(char *)"Query execution was interrupted, query_timeout exceeded"); + MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1907); + } else { + myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,1317,sqlstate,(char *)"Query execution was interrupted"); + MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1317); + } + } else { + int myerr = 0; + // the error code is returned from: + // - mysql_stmt_errno() if using a prepared statement + // - mysql_errno() if not using a prepared statement + if (stmt) { + myerr = mysql_stmt_errno(stmt); + myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,myerr,sqlstate,mysql_stmt_error(stmt)); + } else { + myerr = mysql_errno(_mysql); + myprot->generate_pkt_ERR(false,&pkt.ptr,&pkt.size,sid,myerr,sqlstate,mysql_error(_mysql)); + } + // TODO: Check this is a mysql error + MyHGM->p_update_mysql_error_counter(p_mysql_error_type::mysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, myerr); + } + PSarrayOUT.add(pkt.ptr,pkt.size); + sid++; + resultset_size+=pkt.size; + } + resultset_completed=true; +} + +/* +bool MySQL_ResultSet::get_COM_FIELD_LIST_response(PtrSizeArray *PSarrayFinal) { + transfer_started=true; + if (myprot) { + } + return resultset_completed; +} +*/ + +bool MySQL_ResultSet::get_resultset(PtrSizeArray *PSarrayFinal) { + transfer_started=true; + if (myprot) { + PSarrayFinal->copy_add(&PSarrayOUT,0,PSarrayOUT.len); + while (PSarrayOUT.len) + PSarrayOUT.remove_index(PSarrayOUT.len-1,NULL); + } + return resultset_completed; +} + +void MySQL_ResultSet::buffer_to_PSarrayOut(bool _last) { + if (buffer_used==0) + return; // exit immediately if the buffer is empty + if (buffer_used < RESULTSET_BUFLEN/2) { + if (_last == false) { + buffer=(unsigned char *)realloc(buffer,buffer_used); + } + } + PSarrayOUT.add(buffer,buffer_used); + if (_last) { + buffer = NULL; + } else { + buffer=(unsigned char *)malloc(RESULTSET_BUFLEN); + } + buffer_used=0; +} + +unsigned long long MySQL_ResultSet::current_size() { + unsigned long long intsize=0; + intsize+=sizeof(MySQL_ResultSet); + intsize+=RESULTSET_BUFLEN; // size of buffer + if (PSarrayOUT.len==0) // see bug #699 + return intsize; + intsize+=sizeof(PtrSizeArray); + intsize+=(PSarrayOUT.size*sizeof(PtrSize_t *)); + unsigned int i; + for (i=0; isize>RESULTSET_BUFLEN) { + intsize+=pkt->size; + } else { + intsize+=RESULTSET_BUFLEN; + } + } + return intsize; +} + +/* +my_bool proxy_mysql_stmt_close(MYSQL_STMT* stmt) { + // Clean internal structures for 'stmt->mysql->stmts'. + if (stmt->mysql) { + stmt->mysql->stmts = + list_delete(stmt->mysql->stmts, &stmt->list); + } + // Nullify 'mysql' field to avoid sending a blocking command to the server. + stmt->mysql = NULL; + // Perform the regular close operation. + return mysql_stmt_close(stmt); +} +*/ diff --git a/lib/MySQL_Session.cpp b/lib/MySQL_Session.cpp index 1dc1c19436..ae306e36e4 100644 --- a/lib/MySQL_Session.cpp +++ b/lib/MySQL_Session.cpp @@ -44,6 +44,8 @@ #define SELECT_CONNECTION_ID_LEN 22 #define SELECT_LAST_INSERT_ID "SELECT LAST_INSERT_ID()" #define SELECT_LAST_INSERT_ID_LEN 23 +#define SELECT_LAST_INSERT_ID_FROM_DUAL "SELECT LAST_INSERT_ID() FROM DUAL" +#define SELECT_LAST_INSERT_ID_FROM_DUAL_LEN 33 #define SELECT_LAST_INSERT_ID_LIMIT1 "SELECT LAST_INSERT_ID() LIMIT 1" #define SELECT_LAST_INSERT_ID_LIMIT1_LEN 31 #define SELECT_VARIABLE_IDENTITY "SELECT @@IDENTITY" @@ -1377,92 +1379,19 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) { j["warning_in_hg"] = warning_in_hg; j["gtid"]["hid"] = gtid_hid; j["gtid"]["last"] = ( strlen(gtid_buf) ? gtid_buf : "" ); - j["qpo"]["create_new_connection"] = qpo->create_new_conn; - j["qpo"]["reconnect"] = qpo->reconnect; - j["qpo"]["sticky_conn"] = qpo->sticky_conn; - j["qpo"]["cache_timeout"] = qpo->cache_timeout; - j["qpo"]["cache_ttl"] = qpo->cache_ttl; - j["qpo"]["delay"] = qpo->delay; - j["qpo"]["destination_hostgroup"] = qpo->destination_hostgroup; - j["qpo"]["firewall_whitelist_mode"] = qpo->firewall_whitelist_mode; - j["qpo"]["multiplex"] = qpo->multiplex; - j["qpo"]["timeout"] = qpo->timeout; - j["qpo"]["retries"] = qpo->retries; - j["qpo"]["max_lag_ms"] = qpo->max_lag_ms; + json& jqpo = j["qpo"]; + qpo->get_info_json(jqpo); j["default_schema"] = ( default_schema ? default_schema : "" ); j["user_attributes"] = ( user_attributes ? user_attributes : "" ); j["transaction_persistent"] = transaction_persistent; if (client_myds != NULL) { // only if client_myds is defined - j["client"]["stream"]["pkts_recv"] = client_myds->pkts_recv; - j["client"]["stream"]["pkts_sent"] = client_myds->pkts_sent; - j["client"]["stream"]["bytes_recv"] = client_myds->bytes_info.bytes_recv; - j["client"]["stream"]["bytes_sent"] = client_myds->bytes_info.bytes_sent; - j["client"]["client_addr"]["address"] = ( client_myds->addr.addr ? client_myds->addr.addr : "" ); - j["client"]["client_addr"]["port"] = client_myds->addr.port; - j["client"]["proxy_addr"]["address"] = ( client_myds->proxy_addr.addr ? client_myds->proxy_addr.addr : "" ); - j["client"]["proxy_addr"]["port"] = client_myds->proxy_addr.port; - j["client"]["encrypted"] = client_myds->encrypted; - if (client_myds->encrypted) { - const SSL_CIPHER *cipher = SSL_get_current_cipher(client_myds->ssl); - if (cipher) { - const char * name = SSL_CIPHER_get_name(cipher); - if (name) { - j["client"]["ssl_cipher"] = name; - } - } - } - j["client"]["DSS"] = client_myds->DSS; - j["client"]["switching_auth_sent"] = client_myds->switching_auth_sent; - j["client"]["switching_auth_type"] = client_myds->switching_auth_type; - j["client"]["prot"]["sent_auth_plugin_id"] = client_myds->myprot.sent_auth_plugin_id; - j["client"]["prot"]["auth_plugin_id"] = client_myds->myprot.auth_plugin_id; - switch (client_myds->myprot.auth_plugin_id) { - case AUTH_MYSQL_NATIVE_PASSWORD: - j["client"]["prot"]["auth_plugin"] = "mysql_native_password"; - break; - case AUTH_MYSQL_CLEAR_PASSWORD: - j["client"]["prot"]["auth_plugin"] = "mysql_clear_password"; - break; - case AUTH_MYSQL_CACHING_SHA2_PASSWORD: - j["client"]["prot"]["auth_plugin"] = "caching_sha2_password"; - break; - default: - break; - } - if (client_myds->myconn != NULL) { // only if myconn is defined - if (client_myds->myconn->userinfo != NULL) { // only if userinfo is defined - j["client"]["userinfo"]["username"] = ( client_myds->myconn->userinfo->username ? client_myds->myconn->userinfo->username : "" ); - j["client"]["userinfo"]["schemaname"] = ( client_myds->myconn->userinfo->schemaname ? client_myds->myconn->userinfo->schemaname : "" ); -#ifdef DEBUG - j["client"]["userinfo"]["password"] = ( client_myds->myconn->userinfo->password ? client_myds->myconn->userinfo->password : "" ); -#endif - } - j["conn"]["session_track_gtids"] = ( client_myds->myconn->options.session_track_gtids ? client_myds->myconn->options.session_track_gtids : "") ; - for (auto idx = 0; idx < SQL_NAME_LAST_LOW_WM; idx++) { - client_myds->myconn->variables[idx].fill_client_internal_session(j, idx); - } - { - MySQL_Connection *c = client_myds->myconn; - for (std::vector::const_iterator it_c = c->dynamic_variables_idx.begin(); it_c != c->dynamic_variables_idx.end(); it_c++) { - c->variables[*it_c].fill_client_internal_session(j, *it_c); - } - } - - j["conn"]["autocommit"] = ( client_myds->myconn->options.autocommit ? "ON" : "OFF" ); - j["conn"]["client_flag"]["value"] = client_myds->myconn->options.client_flag; - j["conn"]["client_flag"]["client_found_rows"] = (client_myds->myconn->options.client_flag & CLIENT_FOUND_ROWS ? 1 : 0); - j["conn"]["client_flag"]["client_multi_statements"] = (client_myds->myconn->options.client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0); - j["conn"]["client_flag"]["client_multi_results"] = (client_myds->myconn->options.client_flag & CLIENT_MULTI_RESULTS ? 1 : 0); - j["conn"]["client_flag"]["client_deprecate_eof"] = (client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF ? 1 : 0); - j["conn"]["no_backslash_escapes"] = client_myds->myconn->options.no_backslash_escapes; - j["conn"]["status"]["compression"] = client_myds->myconn->get_status(STATUS_MYSQL_CONNECTION_COMPRESSION); - j["conn"]["ps"]["client_stmt_to_global_ids"] = client_myds->myconn->local_stmts->client_stmt_to_global_ids; - } + client_myds->get_client_myds_info_json(j); } for (unsigned int i=0; ilen; i++) { MySQL_Backend *_mybe = NULL; _mybe=(MySQL_Backend *)mybes->index(i); //unsigned int i = _mybe->hostgroup_id; + j["backends"][i] = {}; j["backends"][i]["hostgroup_id"] = _mybe->hostgroup_id; j["backends"][i]["gtid"] = ( strlen(_mybe->gtid_uuid) ? _mybe->gtid_uuid : "" ); if (_mybe->server_myds) { @@ -1480,86 +1409,8 @@ void MySQL_Session::generate_proxysql_internal_session_json(json &j) { j["backends"][i]["stream"]["bytes_sent"] = _myds->bytes_info.bytes_sent; j["backends"][i]["stream"]["DSS"] = _myds->DSS; if (_myds->myconn) { - MySQL_Connection * _myconn = _myds->myconn; - for (auto idx = 0; idx < SQL_NAME_LAST_LOW_WM; idx++) { - _myconn->variables[idx].fill_server_internal_session(j, i, idx); - } - for (std::vector::const_iterator it_c = _myconn->dynamic_variables_idx.begin(); it_c != _myconn->dynamic_variables_idx.end(); it_c++) { - _myconn->variables[*it_c].fill_server_internal_session(j, i, *it_c); - } - sprintf(buff,"%p",_myconn); - j["backends"][i]["conn"]["address"] = buff; - j["backends"][i]["conn"]["auto_increment_delay_token"] = _myconn->auto_increment_delay_token; - j["backends"][i]["conn"]["bytes_recv"] = _myconn->bytes_info.bytes_recv; - j["backends"][i]["conn"]["bytes_sent"] = _myconn->bytes_info.bytes_sent; - j["backends"][i]["conn"]["questions"] = _myconn->statuses.questions; - j["backends"][i]["conn"]["myconnpoll_get"] = _myconn->statuses.myconnpoll_get; - j["backends"][i]["conn"]["myconnpoll_put"] = _myconn->statuses.myconnpoll_put; - //j["backend"][i]["conn"]["charset"] = _myds->myconn->options.charset; // not used for backend - j["backends"][i]["conn"]["session_track_gtids"] = ( _myconn->options.session_track_gtids ? _myconn->options.session_track_gtids : "") ; - j["backends"][i]["conn"]["init_connect"] = ( _myconn->options.init_connect ? _myconn->options.init_connect : ""); - j["backends"][i]["conn"]["init_connect_sent"] = _myds->myconn->options.init_connect_sent; - j["backends"][i]["conn"]["autocommit"] = ( _myds->myconn->options.autocommit ? "ON" : "OFF" ); - j["backends"][i]["conn"]["last_set_autocommit"] = _myds->myconn->options.last_set_autocommit; - j["backends"][i]["conn"]["no_backslash_escapes"] = _myconn->options.no_backslash_escapes; - j["backends"][i]["conn"]["status"]["get_lock"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_GET_LOCK); - j["backends"][i]["conn"]["status"]["lock_tables"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES); - j["backends"][i]["conn"]["status"]["has_savepoint"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT); - j["backends"][i]["conn"]["status"]["temporary_table"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE); - j["backends"][i]["conn"]["status"]["user_variable"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_USER_VARIABLE); - j["backends"][i]["conn"]["status"]["found_rows"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_FOUND_ROWS); - j["backends"][i]["conn"]["status"]["no_multiplex"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX); - j["backends"][i]["conn"]["status"]["no_multiplex_HG"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); - j["backends"][i]["conn"]["status"]["compression"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_COMPRESSION); - j["backends"][i]["conn"]["status"]["prepared_statement"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT); - j["backends"][i]["conn"]["status"]["has_warnings"] = _myconn->get_status(STATUS_MYSQL_CONNECTION_HAS_WARNINGS); - j["backends"][i]["conn"]["warning_count"] = _myconn->warning_count; - { - // MultiplexDisabled : status returned by MySQL_Connection::MultiplexDisabled(); - // MultiplexDisabled_ext : status returned by MySQL_Connection::MultiplexDisabled() || MySQL_Connection::isActiveTransaction() - bool multiplex_disabled = _myconn->MultiplexDisabled(); - j["backends"][i]["conn"]["MultiplexDisabled"] = multiplex_disabled; - if (multiplex_disabled == false) { - if (_myconn->IsActiveTransaction() == true) { - multiplex_disabled = true; - } - } - j["backends"][i]["conn"]["MultiplexDisabled_ext"] = multiplex_disabled; - } - j["backends"][i]["conn"]["ps"]["backend_stmt_to_global_ids"] = _myconn->local_stmts->backend_stmt_to_global_ids; - j["backends"][i]["conn"]["ps"]["global_stmt_to_backend_ids"] = _myconn->local_stmts->global_stmt_to_backend_ids; - j["backends"][i]["conn"]["client_flag"]["value"] = _myconn->options.client_flag; - j["backends"][i]["conn"]["client_flag"]["client_found_rows"] = (_myconn->options.client_flag & CLIENT_FOUND_ROWS ? 1 : 0); - j["backends"][i]["conn"]["client_flag"]["client_multi_statements"] = (_myconn->options.client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0); - j["backends"][i]["conn"]["client_flag"]["client_deprecate_eof"] = (_myconn->options.client_flag & CLIENT_DEPRECATE_EOF ? 1 : 0); - if (_myconn->mysql && _myconn->ret_mysql) { - MYSQL * _my = _myconn->mysql; - sprintf(buff,"%p",_my); - j["backends"][i]["conn"]["mysql"]["address"] = buff; - j["backends"][i]["conn"]["mysql"]["host"] = ( _my->host ? _my->host : "" ); - j["backends"][i]["conn"]["mysql"]["host_info"] = ( _my->host_info ? _my->host_info : "" ); - j["backends"][i]["conn"]["mysql"]["port"] = _my->port; - j["backends"][i]["conn"]["mysql"]["server_version"] = ( _my->server_version ? _my->server_version : "" ); - j["backends"][i]["conn"]["mysql"]["user"] = ( _my->user ? _my->user : "" ); - j["backends"][i]["conn"]["mysql"]["unix_socket"] = (_my->unix_socket ? _my->unix_socket : ""); - j["backends"][i]["conn"]["mysql"]["db"] = (_my->db ? _my->db : ""); - j["backends"][i]["conn"]["mysql"]["affected_rows"] = _my->affected_rows; - j["backends"][i]["conn"]["mysql"]["insert_id"] = _my->insert_id; - j["backends"][i]["conn"]["mysql"]["thread_id"] = _my->thread_id; - j["backends"][i]["conn"]["mysql"]["server_status"] = _my->server_status; - j["backends"][i]["conn"]["mysql"]["charset"] = _my->charset->nr; - j["backends"][i]["conn"]["mysql"]["charset_name"] = _my->charset->csname; - //j["backends"][i]["conn"]["mysql"][""] = _my->; - //j["backends"][i]["conn"]["mysql"][""] = _my->; - j["backends"][i]["conn"]["mysql"]["options"]["charset_name"] = ( _my->options.charset_name ? _my->options.charset_name : "" ); - j["backends"][i]["conn"]["mysql"]["options"]["use_ssl"] = _my->options.use_ssl; - j["backends"][i]["conn"]["mysql"]["net"]["last_errno"] = _my->net.last_errno; - j["backends"][i]["conn"]["mysql"]["net"]["fd"] = _my->net.fd; - j["backends"][i]["conn"]["mysql"]["net"]["max_packet_size"] = _my->net.max_packet_size; - j["backends"][i]["conn"]["mysql"]["net"]["sqlstate"] = _my->net.sqlstate; - //j["backends"][i]["conn"]["mysql"]["net"][""] = _my->net.; - //j["backends"][i]["conn"]["mysql"]["net"][""] = _my->net.; - } + json& jc = j["backends"][i]["conn"]; + _myds->myconn->get_backend_conn_info_json(jc); } } } @@ -1633,17 +1484,18 @@ bool MySQL_Session::handler_special_queries_STATUS(PtrSize_t *pkt) { string vals[4]; json j = {}; + json& jc = j["conn"]; MySQL_Connection *conn = client_myds->myconn; // here we do a bit back and forth to and from JSON to reuse existing code instead of writing new code. // This is not great for performance, but this query is rarely executed. - conn->variables[SQL_CHARACTER_SET_CLIENT].fill_client_internal_session(j, SQL_CHARACTER_SET_CLIENT); - conn->variables[SQL_CHARACTER_SET_CONNECTION].fill_client_internal_session(j, SQL_CHARACTER_SET_CONNECTION); - conn->variables[SQL_CHARACTER_SET_DATABASE].fill_client_internal_session(j, SQL_CHARACTER_SET_DATABASE); + conn->variables[SQL_CHARACTER_SET_CLIENT].fill_client_internal_session(jc, SQL_CHARACTER_SET_CLIENT); + conn->variables[SQL_CHARACTER_SET_CONNECTION].fill_client_internal_session(jc, SQL_CHARACTER_SET_CONNECTION); + conn->variables[SQL_CHARACTER_SET_DATABASE].fill_client_internal_session(jc, SQL_CHARACTER_SET_DATABASE); - vals[0] = j["conn"][mysql_tracked_variables[SQL_CHARACTER_SET_CLIENT].internal_variable_name]; - vals[1] = j["conn"][mysql_tracked_variables[SQL_CHARACTER_SET_CONNECTION].internal_variable_name]; + vals[0] = jc[mysql_tracked_variables[SQL_CHARACTER_SET_CLIENT].internal_variable_name]; + vals[1] = jc[mysql_tracked_variables[SQL_CHARACTER_SET_CONNECTION].internal_variable_name]; vals[2] = string(mysql_thread___default_variables[SQL_CHARACTER_SET]); - vals[3] = j["conn"][mysql_tracked_variables[SQL_CHARACTER_SET_DATABASE].internal_variable_name]; + vals[3] = jc[mysql_tracked_variables[SQL_CHARACTER_SET_DATABASE].internal_variable_name]; const char *pta[4]; for (int i=0; i<4; i++) { @@ -2458,6 +2310,54 @@ bool MySQL_Session::handler_again___verify_backend_session_track_gtids() { return ret; } + +bool MySQL_Session::handler_again___verify_multiple_variables(MySQL_Connection* myconn) { + for (auto i = 0; i < SQL_NAME_LAST_LOW_WM; i++) { + auto client_hash = client_myds->myconn->var_hash[i]; +#ifdef DEBUG + if (GloVars.global.gdbg) { + switch (i) { + case SQL_CHARACTER_SET: + case SQL_SET_NAMES: + case SQL_CHARACTER_SET_RESULTS: + case SQL_CHARACTER_SET_CONNECTION: + case SQL_CHARACTER_SET_CLIENT: + case SQL_COLLATION_CONNECTION: + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session %p , variable %s has value %s\n" , this, mysql_tracked_variables[i].set_variable_name , client_myds->myconn->variables[i].value); + default: + break; + } + } +#endif // DEBUG + if (client_hash) { + auto server_hash = myconn->var_hash[i]; + if (client_hash != server_hash) { + if(!myconn->var_absent[i] && mysql_variables.verify_variable(this, i)) { + return true; + } + } + } + } + MySQL_Connection *c_con = client_myds->myconn; + vector::const_iterator it_c = c_con->dynamic_variables_idx.begin(); // client connection iterator + for ( ; it_c != c_con->dynamic_variables_idx.end() ; it_c++) { + auto i = *it_c; + auto client_hash = c_con->var_hash[i]; + auto server_hash = myconn->var_hash[i]; + if (client_hash != server_hash) { + if( + !myconn->var_absent[i] + && + mysql_variables.verify_variable(this, i) + ) { + return true; + } + } + } + return false; +} + + /** * @brief Verifies and sets the ldap_user_variable option for the backend connection. * @@ -4143,6 +4043,293 @@ void MySQL_Session::handler___status_WAITING_CLIENT_DATA___default() { } } +int MySQL_Session::GPFC_Statuses2(bool& wrong_pass, PtrSize_t& pkt) { + int handler_ret = 0; + switch (status) { + case WAITING_CLIENT_DATA: + // this case should handled directly into get_pkts_from_client() + assert(0); + break; + case CONNECTING_CLIENT: + switch (client_myds->DSS) { + case STATE_SERVER_HANDSHAKE: + handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(&pkt, &wrong_pass); + break; + case STATE_SSL_INIT: + handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(&pkt, &wrong_pass); + break; + default: + proxy_error("Detected not valid state client state: %d\n", client_myds->DSS); + handler_ret = -1; //close connection + return handler_ret; + break; + } + break; + case FAST_FORWARD: + mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); + break; + // This state is required because it covers the following situation: + // 1. A new connection is created by a client and the 'FAST_FORWARD' mode is enabled. + // 2. The first packet received for this connection isn't a whole packet, i.e, it's either + // split into multiple packets, or it doesn't fit 'queueIN' size (typically + // QUEUE_T_DEFAULT_SIZE). + // 3. Session is still in 'CONNECTING_SERVER' state, BUT further packets remain to be received + // from the initial split packet. + // + // Because of this, packets received during 'CONNECTING_SERVER' when the previous state is + // 'FAST_FORWARD' should be pushed to 'PSarrayOUT'. + case CONNECTING_SERVER: + if (previous_status.empty() == false && previous_status.top() == FAST_FORWARD) { + mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); + break; + } + case session_status___NONE: + default: + handler___status_NONE_or_default(pkt); + handler_ret = -1; + return handler_ret; + break; + } + return handler_ret; +} + +void MySQL_Session::GPFC_DetectedMultiPacket_SetDDS() { + // this is handled only for real traffic, not mirror + switch (client_myds->DSS) { // real traffic only + case STATE_SLEEP: + client_myds->DSS=STATE_SLEEP_MULTI_PACKET; + break; + case STATE_SLEEP_MULTI_PACKET: + break; + default: + // LCOV_EXCL_START + assert(0); + break; + // LCOV_EXCL_STOP + } +} + +int MySQL_Session::GPFC_WaitingClientData_FastForwardSession(PtrSize_t& pkt) { + int handler_ret = 0; + // If this is a 'fast_forward' session that hasn't yet received a backend connection, we don't + // forward 'COM_QUIT' packets, since this will make the act of obtaining a connection pointless. + // Instead, we intercept the 'COM_QUIT' packet and end the 'MySQL_Session'. + unsigned char command = *(static_cast(pkt.ptr)+sizeof(mysql_hdr)); + if (command == _MYSQL_COM_QUIT) { + proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_QUIT packet\n"); + if (GloMyLogger) { GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_QUIT, this, NULL); } + l_free(pkt.size,pkt.ptr); + handler_ret = -1; + return handler_ret; + } + + mybe=find_or_create_backend(current_hostgroup); // set a backend + mybe->server_myds->reinit_queues(); // reinitialize the queues in the myds . By default, they are not active + mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); // move the first packet + previous_status.push(FAST_FORWARD); // next status will be FAST_FORWARD . Now we need a connection + + // If this is a 'fast_forward' session, we impose the 'connect_timeout' prior to actually getting the + // connection from the 'connection_pool'. This is used to ensure that we kill the session if + // 'CONNECTING_SERVER' isn't completed before this timeout expiring. For example, if 'max_connections' + // is reached for the target hostgroup. + if (mybe->server_myds->max_connect_time == 0) { + uint64_t connect_timeout = + mysql_thread___connect_timeout_server < mysql_thread___connect_timeout_server_max ? + mysql_thread___connect_timeout_server_max : mysql_thread___connect_timeout_server; + mybe->server_myds->max_connect_time = thread->curtime + connect_timeout * 1000; + } + // Impose the same connection retrying policy as done for regular connections during + // 'MYSQL_CON_QUERY'. + mybe->server_myds->connect_retries_on_failure = mysql_thread___connect_retries_on_failure; + // 'CurrentQuery' isn't used for 'FAST_FORWARD' but we update it for using it as a session + // startup time for when a fast_forward session has attempted to obtain a connection. + CurrentQuery.start_time=thread->curtime; + + //NEXT_IMMEDIATE(CONNECTING_SERVER); // we create a connection . next status will be FAST_FORWARD + // we can't use NEXT_IMMEDIATE() inside get_pkts_from_client() + // instead we set status to CONNECTING_SERVER and return 0 + // when we exit from get_pkts_from_client() we expect the label "handler_again" + set_status(CONNECTING_SERVER); + + return handler_ret; +} + + +void MySQL_Session::GPFC_PreparedStatements(PtrSize_t& pkt, unsigned char c) { + switch ((enum_mysql_command)c) { + case _MYSQL_COM_STMT_PREPARE: + if (GloMyLdapAuth) { + if (session_type==PROXYSQL_SESSION_MYSQL) { + if (mysql_thread___add_ldap_user_comment && strlen(mysql_thread___add_ldap_user_comment)) { + add_ldap_comment_to_pkt(&pkt); + } + } + } + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_PREPARE(pkt); + break; + case _MYSQL_COM_STMT_EXECUTE: + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_EXECUTE(pkt); + break; + case _MYSQL_COM_STMT_RESET: + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_RESET(pkt); + break; + case _MYSQL_COM_STMT_CLOSE: + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_CLOSE(pkt); + break; + case _MYSQL_COM_STMT_SEND_LONG_DATA: + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_SEND_LONG_DATA(pkt); + break; + default: + // LCOV_EXCL_START + assert(0); + break; + // LCOV_EXCL_STOP + } +} + +void MySQL_Session::GPFC_Replication_SwitchToFastForward(PtrSize_t& pkt, unsigned char c) { + // In this switch we handle commands that download binlog events from MySQL + // servers. For these commands a lot of the features provided by ProxySQL + // aren't useful, like multiplexing, query parsing, etc. For this reason, + // ProxySQL enables fast_forward when it receives these commands.  + { + // we use a switch to write the command in the info message + std::string q = "Received command "; + switch ((enum_mysql_command)c) { + case _MYSQL_COM_BINLOG_DUMP: + q += "MYSQL_COM_BINLOG_DUMP"; + break; + case _MYSQL_COM_BINLOG_DUMP_GTID: + q += "MYSQL_COM_BINLOG_DUMP_GTID"; + break; + case _MYSQL_COM_REGISTER_SLAVE: + q += "MYSQL_COM_REGISTER_SLAVE"; + break; + default: + assert(0); + break; + }; + // we add the client details in the info message + if (client_myds && client_myds->addr.addr) { + q += " from client " + std::string(client_myds->addr.addr) + ":" + std::to_string(client_myds->addr.port); + } + q += " . Changing session fast_forward to true"; + proxy_info("%s\n", q.c_str()); + } + session_fast_forward = true; + + if (client_myds->PSarrayIN->len) { + proxy_error("UNEXPECTED PACKET FROM CLIENT -- PLEASE REPORT A BUG\n"); + assert(0); + } + client_myds->PSarrayIN->add(pkt.ptr, pkt.size); + + // The following code prepares the session as if it was configured with fast + // forward before receiving the command. This way the state machine will + // handle the command automatically. + current_hostgroup = previous_hostgroup; + mybe = find_or_create_backend(current_hostgroup); // set a backend + mybe->server_myds->reinit_queues(); // reinitialize the queues in the myds . By default, they are not active + // We reinitialize the 'wait_until' since this session shouldn't wait for processing as + // we are now transitioning to 'FAST_FORWARD'. + mybe->server_myds->wait_until = 0; + if (mybe->server_myds->DSS==STATE_NOT_INITIALIZED) { + // NOTE: This section is entirely borrowed from 'STATE_SLEEP' for 'session_fast_forward'. + // Check comments there for extra information. + // ============================================================================= + if (mybe->server_myds->max_connect_time == 0) { + uint64_t connect_timeout = + mysql_thread___connect_timeout_server < mysql_thread___connect_timeout_server_max ? + mysql_thread___connect_timeout_server_max : mysql_thread___connect_timeout_server; + mybe->server_myds->max_connect_time = thread->curtime + connect_timeout * 1000; + } + mybe->server_myds->connect_retries_on_failure = mysql_thread___connect_retries_on_failure; + CurrentQuery.start_time=thread->curtime; + // ============================================================================= + + // we don't have a connection + previous_status.push(FAST_FORWARD); // next status will be FAST_FORWARD + set_status(CONNECTING_SERVER); // now we need a connection + } else { + // In case of having a connection, we need to make user to reset the state machine + // for current server 'MySQL_Data_Stream', setting it outside of any state handled + // by 'mariadb' library. Otherwise 'MySQL_Thread' will threat this + // 'MySQL_Data_Stream' as library handled. + mybe->server_myds->DSS = STATE_READY; + // myds needs to have encrypted value set correctly + { + MySQL_Data_Stream * myds = mybe->server_myds; + MySQL_Connection * myconn = myds->myconn; + assert(myconn != NULL); + // PMC-10005 + // if backend connection uses SSL we will set + // encrypted = true and we will start using the SSL structure + // directly from P_MARIADB_TLS structure. + MYSQL *mysql = myconn->mysql; + if (mysql && myconn->ret_mysql) { + if (mysql->options.use_ssl == 1) { + P_MARIADB_TLS * matls = (P_MARIADB_TLS *)mysql->net.pvio->ctls; + if (matls != NULL) { + myds->encrypted = true; + myds->ssl = (SSL *)matls->ssl; + myds->rbio_ssl = BIO_new(BIO_s_mem()); + myds->wbio_ssl = BIO_new(BIO_s_mem()); + SSL_set_bio(myds->ssl, myds->rbio_ssl, myds->wbio_ssl); + } else { + // if mysql->options.use_ssl == 1 but matls == NULL + // it means that ProxySQL tried to use SSL to connect to the backend + // but the backend didn't support SSL + } + } + } + } + set_status(FAST_FORWARD); // we can set status to FAST_FORWARD + } +} + +bool MySQL_Session::GPFC_QueryUSE(PtrSize_t& pkt, int& handler_ret) { + bool need_break = false; + // This block was moved from 'handler_special_queries' to support + // handling of 'USE' statements which are preceded by a comment. + // For more context check issue: #3493. + if (session_type != PROXYSQL_SESSION_CLICKHOUSE) { + const char *qd = CurrentQuery.get_digest_text(); + bool use_db_query = false; + + if (qd != NULL) { + if ( + (strncasecmp((char *)"USE",qd,3)==0) + && + ( + (strncasecmp((char *)"USE ",qd,4)==0) + || + (strncasecmp((char *)"USE`",qd,4)==0) + ) + ) { + use_db_query = true; + } + } else { + if (pkt.size > (5+4) && strncasecmp((char *)"USE ", (char *)pkt.ptr+5, 4) == 0) { + use_db_query = true; + } + } + + if (use_db_query) { + handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DB(&pkt); + + if (mirror == false) { + need_break = true; + return need_break; + } else { + handler_ret = -1; + return need_break; + } + } + } + + return need_break; +} + int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { int handler_ret = 0; unsigned char c; @@ -4158,39 +4345,9 @@ int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { client_myds->PSarrayIN->remove_index(0,&pkt); } switch (status) { - - case CONNECTING_CLIENT: - switch (client_myds->DSS) { - case STATE_SERVER_HANDSHAKE: - handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(&pkt, &wrong_pass); - break; - case STATE_SSL_INIT: - handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(&pkt, &wrong_pass); - break; - default: - proxy_error("Detected not valid state client state: %d\n", client_myds->DSS); - handler_ret = -1; //close connection - return handler_ret; - break; - } - break; - case WAITING_CLIENT_DATA: - // this is handled only for real traffic, not mirror - if (pkt.size==(0xFFFFFF+sizeof(mysql_hdr))) { - // we are handling a multi-packet - switch (client_myds->DSS) { // real traffic only - case STATE_SLEEP: - client_myds->DSS=STATE_SLEEP_MULTI_PACKET; - break; - case STATE_SLEEP_MULTI_PACKET: - break; - default: - // LCOV_EXCL_START - assert(0); - break; - // LCOV_EXCL_STOP - } + if (pkt.size==(0xFFFFFF+sizeof(mysql_hdr))) { // we are handling a multi-packet + GPFC_DetectedMultiPacket_SetDDS(); } switch (client_myds->DSS) { case STATE_SLEEP_MULTI_PACKET: @@ -4217,51 +4374,13 @@ int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { } } proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , client_myds=%p . Statuses: WAITING_CLIENT_DATA - STATE_SLEEP\n", this, client_myds); - if (session_fast_forward==true) { // if it is fast forward - // If this is a 'fast_forward' session that hasn't yet received a backend connection, we don't - // forward 'COM_QUIT' packets, since this will make the act of obtaining a connection pointless. - // Instead, we intercept the 'COM_QUIT' packet and end the 'MySQL_Session'. - unsigned char command = *(static_cast(pkt.ptr)+sizeof(mysql_hdr)); - if (command == _MYSQL_COM_QUIT) { - proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_QUIT packet\n"); - if (GloMyLogger) { GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_QUIT, this, NULL); } - l_free(pkt.size,pkt.ptr); - handler_ret = -1; - return handler_ret; - } - mybe=find_or_create_backend(current_hostgroup); // set a backend - mybe->server_myds->reinit_queues(); // reinitialize the queues in the myds . By default, they are not active - mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); // move the first packet - previous_status.push(FAST_FORWARD); // next status will be FAST_FORWARD . Now we need a connection - - // If this is a 'fast_forward' session, we impose the 'connect_timeout' prior to actually getting the - // connection from the 'connection_pool'. This is used to ensure that we kill the session if - // 'CONNECTING_SERVER' isn't completed before this timeout expiring. For example, if 'max_connections' - // is reached for the target hostgroup. - if (mybe->server_myds->max_connect_time == 0) { - uint64_t connect_timeout = - mysql_thread___connect_timeout_server < mysql_thread___connect_timeout_server_max ? - mysql_thread___connect_timeout_server_max : mysql_thread___connect_timeout_server; - mybe->server_myds->max_connect_time = thread->curtime + connect_timeout * 1000; - } - // Impose the same connection retrying policy as done for regular connections during - // 'MYSQL_CON_QUERY'. - mybe->server_myds->connect_retries_on_failure = mysql_thread___connect_retries_on_failure; - // 'CurrentQuery' isn't used for 'FAST_FORWARD' but we update it for using it as a session - // startup time for when a fast_forward session has attempted to obtain a connection. - CurrentQuery.start_time=thread->curtime; - - { - //NEXT_IMMEDIATE(CONNECTING_SERVER); // we create a connection . next status will be FAST_FORWARD - // we can't use NEXT_IMMEDIATE() inside get_pkts_from_client() - // instead we set status to CONNECTING_SERVER and return 0 - // when we exit from get_pkts_from_client() we expect the label "handler_again" - set_status(CONNECTING_SERVER); - return 0; - } + if (session_fast_forward==true) { // if it is fast forward + handler_ret = GPFC_WaitingClientData_FastForwardSession(pkt); + return handler_ret; } c=*((unsigned char *)pkt.ptr+sizeof(mysql_hdr)); + if (session_type == PROXYSQL_SESSION_CLICKHOUSE) { if ((enum_mysql_command)c == _MYSQL_COM_INIT_DB) { handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_INIT_DB_replace_CLICKHOUSE(pkt); @@ -4319,44 +4438,16 @@ int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { (begint.tv_sec*1000000000+begint.tv_nsec); } assert(qpo); // GloQPro->process_mysql_query() should always return a qpo - // This block was moved from 'handler_special_queries' to support - // handling of 'USE' statements which are preceded by a comment. - // For more context check issue: #3493. - // =================================================== - if (session_type != PROXYSQL_SESSION_CLICKHOUSE) { - const char *qd = CurrentQuery.get_digest_text(); - bool use_db_query = false; - - if (qd != NULL) { - if ( - (strncasecmp((char *)"USE",qd,3)==0) - && - ( - (strncasecmp((char *)"USE ",qd,4)==0) - || - (strncasecmp((char *)"USE`",qd,4)==0) - ) - ) { - use_db_query = true; - } - } else { - if (pkt.size > (5+4) && strncasecmp((char *)"USE ", (char *)pkt.ptr+5, 4) == 0) { - use_db_query = true; - } - } - - if (use_db_query) { - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY_USE_DB(&pkt); - if (mirror == false) { - break; - } else { - handler_ret = -1; - return handler_ret; - } + { + bool need_break = GPFC_QueryUSE(pkt, handler_ret); + if (handler_ret == -1) { + return handler_ret; + } else if (need_break == true) { + break; } } - // =================================================== + if (qpo->max_lag_ms >= 0) { thread->status_variables.stvar[st_var_queries_with_max_lag_ms]++; } @@ -4462,128 +4553,16 @@ int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { } break; case _MYSQL_COM_STMT_PREPARE: - if (GloMyLdapAuth) { - if (session_type==PROXYSQL_SESSION_MYSQL) { - if (mysql_thread___add_ldap_user_comment && strlen(mysql_thread___add_ldap_user_comment)) { - add_ldap_comment_to_pkt(&pkt); - } - } - } - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_PREPARE(pkt); - break; case _MYSQL_COM_STMT_EXECUTE: - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_EXECUTE(pkt); - break; case _MYSQL_COM_STMT_RESET: - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_RESET(pkt); - break; case _MYSQL_COM_STMT_CLOSE: - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_CLOSE(pkt); - break; case _MYSQL_COM_STMT_SEND_LONG_DATA: - handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_STMT_SEND_LONG_DATA(pkt); + GPFC_PreparedStatements(pkt, c); break; case _MYSQL_COM_BINLOG_DUMP: case _MYSQL_COM_BINLOG_DUMP_GTID: case _MYSQL_COM_REGISTER_SLAVE: - // In this switch we handle commands that download binlog events from MySQL - // servers. For these commands a lot of the features provided by ProxySQL - // aren't useful, like multiplexing, query parsing, etc. For this reason, - // ProxySQL enables fast_forward when it receives these commands.  - { - // we use a switch to write the command in the info message - std::string q = "Received command "; - switch ((enum_mysql_command)c) { - case _MYSQL_COM_BINLOG_DUMP: - q += "MYSQL_COM_BINLOG_DUMP"; - break; - case _MYSQL_COM_BINLOG_DUMP_GTID: - q += "MYSQL_COM_BINLOG_DUMP_GTID"; - break; - case _MYSQL_COM_REGISTER_SLAVE: - q += "MYSQL_COM_REGISTER_SLAVE"; - break; - default: - assert(0); - break; - }; - // we add the client details in the info message - if (client_myds && client_myds->addr.addr) { - q += " from client " + std::string(client_myds->addr.addr) + ":" + std::to_string(client_myds->addr.port); - } - q += " . Changing session fast_forward to true"; - proxy_info("%s\n", q.c_str()); - } - session_fast_forward = true; - - if (client_myds->PSarrayIN->len) { - proxy_error("UNEXPECTED PACKET FROM CLIENT -- PLEASE REPORT A BUG\n"); - assert(0); - } - client_myds->PSarrayIN->add(pkt.ptr, pkt.size); - - // The following code prepares the session as if it was configured with fast - // forward before receiving the command. This way the state machine will - // handle the command automatically. - current_hostgroup = previous_hostgroup; - mybe = find_or_create_backend(current_hostgroup); // set a backend - mybe->server_myds->reinit_queues(); // reinitialize the queues in the myds . By default, they are not active - // We reinitialize the 'wait_until' since this session shouldn't wait for processing as - // we are now transitioning to 'FAST_FORWARD'. - mybe->server_myds->wait_until = 0; - if (mybe->server_myds->DSS==STATE_NOT_INITIALIZED) { - // NOTE: This section is entirely borrowed from 'STATE_SLEEP' for 'session_fast_forward'. - // Check comments there for extra information. - // ============================================================================= - if (mybe->server_myds->max_connect_time == 0) { - uint64_t connect_timeout = - mysql_thread___connect_timeout_server < mysql_thread___connect_timeout_server_max ? - mysql_thread___connect_timeout_server_max : mysql_thread___connect_timeout_server; - mybe->server_myds->max_connect_time = thread->curtime + connect_timeout * 1000; - } - mybe->server_myds->connect_retries_on_failure = mysql_thread___connect_retries_on_failure; - CurrentQuery.start_time=thread->curtime; - // ============================================================================= - - // we don't have a connection - previous_status.push(FAST_FORWARD); // next status will be FAST_FORWARD - set_status(CONNECTING_SERVER); // now we need a connection - } else { - // In case of having a connection, we need to make user to reset the state machine - // for current server 'MySQL_Data_Stream', setting it outside of any state handled - // by 'mariadb' library. Otherwise 'MySQL_Thread' will threat this - // 'MySQL_Data_Stream' as library handled. - mybe->server_myds->DSS = STATE_READY; - // myds needs to have encrypted value set correctly - { - MySQL_Data_Stream * myds = mybe->server_myds; - MySQL_Connection * myconn = myds->myconn; - assert(myconn != NULL); - // PMC-10005 - // if backend connection uses SSL we will set - // encrypted = true and we will start using the SSL structure - // directly from P_MARIADB_TLS structure. - MYSQL *mysql = myconn->mysql; - if (mysql && myconn->ret_mysql) { - if (mysql->options.use_ssl == 1) { - P_MARIADB_TLS * matls = (P_MARIADB_TLS *)mysql->net.pvio->ctls; - if (matls != NULL) { - myds->encrypted = true; - myds->ssl = (SSL *)matls->ssl; - myds->rbio_ssl = BIO_new(BIO_s_mem()); - myds->wbio_ssl = BIO_new(BIO_s_mem()); - SSL_set_bio(myds->ssl, myds->rbio_ssl, myds->wbio_ssl); - } else { - // if mysql->options.use_ssl == 1 but matls == NULL - // it means that ProxySQL tried to use SSL to connect to the backend - // but the backend didn't support SSL - } - } - } - } - set_status(FAST_FORWARD); // we can set status to FAST_FORWARD - } - + GPFC_Replication_SwitchToFastForward(pkt, c); break; case _MYSQL_COM_QUIT: proxy_debug(PROXY_DEBUG_MYSQL_COM, 5, "Got COM_QUIT packet\n"); @@ -4614,65 +4593,57 @@ int MySQL_Session::get_pkts_from_client(bool& wrong_pass, PtrSize_t& pkt) { break; } break; - case FAST_FORWARD: - mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); - break; - // This state is required because it covers the following situation: - // 1. A new connection is created by a client and the 'FAST_FORWARD' mode is enabled. - // 2. The first packet received for this connection isn't a whole packet, i.e, it's either - // split into multiple packets, or it doesn't fit 'queueIN' size (typically - // QUEUE_T_DEFAULT_SIZE). - // 3. Session is still in 'CONNECTING_SERVER' state, BUT further packets remain to be received - // from the initial split packet. - // - // Because of this, packets received during 'CONNECTING_SERVER' when the previous state is - // 'FAST_FORWARD' should be pushed to 'PSarrayOUT'. - case CONNECTING_SERVER: - if (previous_status.empty() == false && previous_status.top() == FAST_FORWARD) { - mybe->server_myds->PSarrayOUT->add(pkt.ptr, pkt.size); - break; - } - case session_status___NONE: default: - handler___status_NONE_or_default(pkt); - handler_ret = -1; - return handler_ret; - break; + handler_ret = GPFC_Statuses2(wrong_pass, pkt); + if (handler_ret != 0) { + return handler_ret; + } } } return handler_ret; } // end of MySQL_Session::get_pkts_from_client() +/** + * @brief Handler for processing query errors and checking backend connection status. + * + * This function handles query errors and checks the status of the backend connection. + * It evaluates the server status and takes appropriate actions based on specific conditions. + * + * @param myds Pointer to the MySQL data stream associated with the session. + * + * @return Returns an integer status code indicating the result of the error handling: + * - 0: No action required, query processing can continue. + * - 1: Retry required due to backend connection status, query processing needs to be retried. + * - -1: Error encountered, query processing cannot continue. + */ -// this function returns: -// 0 : no action -// -1 : the calling function will return -// 1 : call to NEXT_IMMEDIATE int MySQL_Session::handler_ProcessingQueryError_CheckBackendConnectionStatus(MySQL_Data_Stream *myds) { MySQL_Connection *myconn = myds->myconn; // the query failed - if ( - // due to #774 , we now read myconn->server_status instead of myconn->parent->status - (myconn->server_status==MYSQL_SERVER_STATUS_OFFLINE_HARD) // the query failed because the server is offline hard - || - (myconn->server_status==MYSQL_SERVER_STATUS_SHUNNED && myconn->parent->shunned_automatic==true && myconn->parent->shunned_and_kill_all_connections==true) // the query failed because the server is shunned due to a serious failure - || - (myconn->server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) // slave is lagging! see #774 - ) { + if (myconn->IsServerOffline()) { + // Set maximum connect time if connect timeout is configured if (mysql_thread___connect_timeout_server_max) { myds->max_connect_time=thread->curtime+mysql_thread___connect_timeout_server_max*1000; } + + // Variables to track retry and error conditions bool retry_conn=false; if (myconn->server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) { thread->status_variables.stvar[st_var_backend_lagging_during_query]++; proxy_error("Detected a lagging server during query: %s, %d, session_id:%u\n", myconn->parent->address, myconn->parent->port, this->thread_session_id); MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_LAGGING_SRV); + } else if (myconn->server_status == MYSQL_SERVER_STATUS_ONLINE && myconn->parent->myhgc->online_servers_within_threshold() == false) { + //proxy_error("Number of online servers detected in a hostgroup exceeds the configured maximum online servers. %s, %d, hostgroup:%u, num_online_servers:%u, max_online_servers:%u, session_id:%u\n", + // myconn->parent->address, myconn->parent->port, myconn->parent->myhgc->hid, num_online_servers, myconn->parent->myhgc->attributes.max_num_online_servers, this->thread_session_id); + myconn->parent->myhgc->log_num_online_server_count_error(); } else { thread->status_variables.stvar[st_var_backend_offline_during_query]++; proxy_error("Detected an offline server during query: %s, %d, session_id:%u\n", myconn->parent->address, myconn->parent->port, this->thread_session_id); MyHGM->p_update_mysql_error_counter(p_mysql_error_type::proxysql, myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, ER_PROXYSQL_OFFLINE_SRV); } + + // Retry the query if retries are allowed and conditions permit if (myds->query_retries_on_failure > 0) { myds->query_retries_on_failure--; if ((myds->myconn->reusable==true) && myds->myconn->IsActiveTransaction()==false && myds->myconn->MultiplexDisabled()==false) { @@ -4684,6 +4655,8 @@ int MySQL_Session::handler_ProcessingQueryError_CheckBackendConnectionStatus(MyS } } } + + // Destroy MySQL connection from pool and reset file descriptor myds->destroy_MySQL_Connection_From_Pool(false); myds->fd=0; if (retry_conn) { @@ -5032,6 +5005,24 @@ void MySQL_Session::handler_minus1_HandleBackendConnection(MySQL_Data_Stream *my } // this function was inline +/** + * @brief Run a MySQL query on the current session. + * + * This function executes a MySQL query on the current session based on its status. + * The query execution is asynchronous and handled by the specified MySQL connection. + * It returns the result code of the asynchronous query execution. + * + * @param myds Pointer to the MySQL data stream associated with the session. + * @param myconn Pointer to the MySQL connection used to execute the query. + * + * @return Returns an integer status code indicating the result of the query execution, + * as returned by async_query(): + * - 0: Query execution completed successfully. + * - -1: Query execution failed. + * - 1: Query execution in progress. + * - 2: Processing a multi-statement query, control needs to be transferred to MySQL_Session. + * - 3: In the middle of processing a multi-statement query. + */ int MySQL_Session::RunQuery(MySQL_Data_Stream *myds, MySQL_Connection *myconn) { PROXY_TRACE2(); int rc = 0; @@ -5147,6 +5138,55 @@ void MySQL_Session::handler_rc0_Process_GTID(MySQL_Connection *myconn) { } } +void MySQL_Session::handler_KillConnectionIfNeeded() { + if ( // two conditions + // If the server connection is in a non-idle state (ASYNC_IDLE), and the current time is greater than or equal to mybe->server_myds->wait_until + // This indicates that the server is taking too long to respond. + (mybe->server_myds->myconn && mybe->server_myds->myconn->async_state_machine!=ASYNC_IDLE && mybe->server_myds->wait_until && thread->curtime >= mybe->server_myds->wait_until) + || + // If the session has been marked as killed by an admin. + (killed==true) + ) { // Logging and Action + // we only log in case on timing out here. Logging for 'killed' is done in the places that hold that contextual information. + if (mybe->server_myds->myconn && (mybe->server_myds->myconn->async_state_machine != ASYNC_IDLE) && mybe->server_myds->wait_until && (thread->curtime >= mybe->server_myds->wait_until)) { + std::string query {}; + + if (CurrentQuery.stmt_info == NULL) { // text protocol + query = std::string { mybe->server_myds->myconn->query.ptr, mybe->server_myds->myconn->query.length }; + } else { // prepared statement + query = std::string { CurrentQuery.stmt_info->query, CurrentQuery.stmt_info->query_length }; + } + + std::string client_addr { "" }; + int client_port = 0; + + if (client_myds) { + client_addr = client_myds->addr.addr ? client_myds->addr.addr : ""; + client_port = client_myds->addr.port; + } + // it logs a warning message indicating the query that caused the timeout, along with client details. + proxy_warning( + "Killing connection %s:%d because query '%s' from client '%s':%d timed out.\n", + mybe->server_myds->myconn->parent->address, + mybe->server_myds->myconn->parent->port, + query.c_str(), + client_addr.c_str(), + client_port + ); + } + // it calls handler_again___new_thread_to_kill_connection() to initiate the killing of the connection associated with the session that timed out. + handler_again___new_thread_to_kill_connection(); + } +} + +void MySQL_Session::handler_rc0_RefreshActiveTransactions(MySQL_Connection* myconn) { + if ((myconn->mysql->server_status & SERVER_STATUS_IN_TRANS) == 0) { // there is no transaction on the backend connection + active_transactions = NumActiveTransactions(); // we check all the hostgroups/backends + if (active_transactions == 0) + transaction_started_at = 0; // reset it + } +} + int MySQL_Session::handler() { int handler_ret = 0; bool prepared_stmt_with_no_params = false; @@ -5250,44 +5290,8 @@ int MySQL_Session::handler() { // set max_connect_time to zero, indicating no timeout mybe->server_myds->max_connect_time=0; } - if ( // two conditions - // If the server connection is in a non-idle state (ASYNC_IDLE), and the current time is greater than or equal to mybe->server_myds->wait_until - // This indicates that the server is taking too long to respond. - (mybe->server_myds->myconn && mybe->server_myds->myconn->async_state_machine!=ASYNC_IDLE && mybe->server_myds->wait_until && thread->curtime >= mybe->server_myds->wait_until) - || - // If the session has been marked as killed by an admin. - (killed==true) - ) { // Logging and Action - // we only log in case on timing out here. Logging for 'killed' is done in the places that hold that contextual information. - if (mybe->server_myds->myconn && (mybe->server_myds->myconn->async_state_machine != ASYNC_IDLE) && mybe->server_myds->wait_until && (thread->curtime >= mybe->server_myds->wait_until)) { - std::string query {}; - - if (CurrentQuery.stmt_info == NULL) { // text protocol - query = std::string { mybe->server_myds->myconn->query.ptr, mybe->server_myds->myconn->query.length }; - } else { // prepared statement - query = std::string { CurrentQuery.stmt_info->query, CurrentQuery.stmt_info->query_length }; - } - - std::string client_addr { "" }; - int client_port = 0; + handler_KillConnectionIfNeeded(); - if (client_myds) { - client_addr = client_myds->addr.addr ? client_myds->addr.addr : ""; - client_port = client_myds->addr.port; - } - // it logs a warning message indicating the query that caused the timeout, along with client details. - proxy_warning( - "Killing connection %s:%d because query '%s' from client '%s':%d timed out.\n", - mybe->server_myds->myconn->parent->address, - mybe->server_myds->myconn->parent->port, - query.c_str(), - client_addr.c_str(), - client_port - ); - } - // it calls handler_again___new_thread_to_kill_connection() to initiate the killing of the connection associated with the session that timed out. - handler_again___new_thread_to_kill_connection(); - } // checks if the backend MySQL server associated with the session has been initialized (STATE_NOT_INITIALIZED) if (mybe->server_myds->DSS==STATE_NOT_INITIALIZED) { // we don't have a backend yet @@ -5337,47 +5341,8 @@ int MySQL_Session::handler() { goto handler_again; } - for (auto i = 0; i < SQL_NAME_LAST_LOW_WM; i++) { - auto client_hash = client_myds->myconn->var_hash[i]; -#ifdef DEBUG - if (GloVars.global.gdbg) { - switch (i) { - case SQL_CHARACTER_SET: - case SQL_SET_NAMES: - case SQL_CHARACTER_SET_RESULTS: - case SQL_CHARACTER_SET_CONNECTION: - case SQL_CHARACTER_SET_CLIENT: - case SQL_COLLATION_CONNECTION: - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 7, "Session %p , variable %s has value %s\n" , this, mysql_tracked_variables[i].set_variable_name , client_myds->myconn->variables[i].value); - default: - break; - } - } -#endif // DEBUG - if (client_hash) { - auto server_hash = myconn->var_hash[i]; - if (client_hash != server_hash) { - if(!myconn->var_absent[i] && mysql_variables.verify_variable(this, i)) { - goto handler_again; - } - } - } - } - MySQL_Connection *c_con = client_myds->myconn; - vector::const_iterator it_c = c_con->dynamic_variables_idx.begin(); // client connection iterator - for ( ; it_c != c_con->dynamic_variables_idx.end() ; it_c++) { - auto i = *it_c; - auto client_hash = c_con->var_hash[i]; - auto server_hash = myconn->var_hash[i]; - if (client_hash != server_hash) { - if( - !myconn->var_absent[i] - && - mysql_variables.verify_variable(this, i) - ) { - goto handler_again; - } - } + if (handler_again___verify_multiple_variables(myconn) == true) { + goto handler_again; } if (locked_on_hostgroup != -1) { @@ -5446,11 +5411,7 @@ int MySQL_Session::handler() { if (rc==0) { if (active_transactions != 0) { // run this only if currently we think there is a transaction - if ((myconn->mysql->server_status & SERVER_STATUS_IN_TRANS) == 0) { // there is no transaction on the backend connection - active_transactions = NumActiveTransactions(); // we check all the hostgroups/backends - if (active_transactions == 0) - transaction_started_at = 0; // reset it - } + handler_rc0_RefreshActiveTransactions(myconn); } handler_rc0_Process_GTID(myconn); @@ -5594,7 +5555,7 @@ int MySQL_Session::handler() { } } - goto __exit_DSS__STATE_NOT_INITIALIZED; + //goto __exit_DSS__STATE_NOT_INITIALIZED; } @@ -5619,44 +5580,10 @@ int MySQL_Session::handler() { break; case SHOW_WARNINGS: - // Performs a 'SHOW WARNINGS' query over the current backend connection and returns the connection back - // to the connection pool when finished. Actual logging of received warnings is performed in - // 'MySQL_Connection' while processing 'ASYNC_USE_RESULT_CONT'. - { - MySQL_Data_Stream *myds=mybe->server_myds; - MySQL_Connection *myconn=myds->myconn; - - // Setting POLLOUT is required just in case this state has been reached when 'RunQuery' from - // 'PROCESSING_QUERY' state has immediately return. This is because in case 'mysql_real_query_start' - // immediately returns with '0' the session is never processed again by 'MySQL_Thread', and 'revents' is - // never updated with the result of polling through the 'MySQL_Thread::mypolls'. - myds->revents |= POLLOUT; - - int rc = myconn->async_query( - mybe->server_myds->revents,(char *)"SHOW WARNINGS", strlen((char *)"SHOW WARNINGS") - ); - if (rc == 0 || rc == -1) { - // Cleanup the connection resulset from 'SHOW WARNINGS' for the next query. - if (myconn->MyRS != NULL) { - delete myconn->MyRS; - myconn->MyRS = NULL; - } - - if (rc == -1) { - int myerr = mysql_errno(myconn->mysql); - proxy_error( - "'SHOW WARNINGS' failed to be executed over backend connection with error: '%d'\n", myerr - ); - } - - RequestEnd(myds); - finishQuery(myds,myconn,prepared_stmt_with_no_params); - - handler_ret = 0; - return handler_ret; - } else { - goto handler_again; - } + if (handler_again___status_SHOW_WARNINGS(mybe->server_myds, prepared_stmt_with_no_params) == true) { + goto handler_again; + } else { + handler_ret = 0; return handler_ret; } break; @@ -5665,8 +5592,8 @@ int MySQL_Session::handler() { int rc=0; if (handler_again___status_CONNECTING_SERVER(&rc)) goto handler_again; // we changed status - if (rc==1) //handler_again___status_CONNECTING_SERVER returns 1 - goto __exit_DSS__STATE_NOT_INITIALIZED; + //if (rc==1) //handler_again___status_CONNECTING_SERVER returns 1 + // goto __exit_DSS__STATE_NOT_INITIALIZED; } break; case session_status___NONE: @@ -5685,7 +5612,7 @@ int MySQL_Session::handler() { } -__exit_DSS__STATE_NOT_INITIALIZED: +//__exit_DSS__STATE_NOT_INITIALIZED: #ifdef DEBUG @@ -5711,6 +5638,46 @@ int MySQL_Session::handler() { } // end ::handler() + +bool MySQL_Session::handler_again___status_SHOW_WARNINGS(MySQL_Data_Stream* myds, bool prepared_stmt_with_no_params) { + // Performs a 'SHOW WARNINGS' query over the current backend connection and returns the connection back + // to the connection pool when finished. Actual logging of received warnings is performed in + // 'MySQL_Connection' while processing 'ASYNC_USE_RESULT_CONT'. + MySQL_Connection *myconn=myds->myconn; + + // Setting POLLOUT is required just in case this state has been reached when 'RunQuery' from + // 'PROCESSING_QUERY' state has immediately return. This is because in case 'mysql_real_query_start' + // immediately returns with '0' the session is never processed again by 'MySQL_Thread', and 'revents' is + // never updated with the result of polling through the 'MySQL_Thread::mypolls'. + myds->revents |= POLLOUT; + + int rc = myconn->async_query( + myds->revents,(char *)"SHOW WARNINGS", strlen((char *)"SHOW WARNINGS") + ); + if (rc == 0 || rc == -1) { + // Cleanup the connection resulset from 'SHOW WARNINGS' for the next query. + if (myconn->MyRS != NULL) { + delete myconn->MyRS; + myconn->MyRS = NULL; + } + + if (rc == -1) { + int myerr = mysql_errno(myconn->mysql); + proxy_error( + "'SHOW WARNINGS' failed to be executed over backend connection with error: '%d'\n", myerr + ); + } + + RequestEnd(myds); + finishQuery(myds,myconn,prepared_stmt_with_no_params); + + return false; + } else { + return true; // goto handler_again + } + return false; // default +} + /** * @brief Handle multiple session statuses. * @@ -5755,6 +5722,72 @@ bool MySQL_Session::handler_again___multiple_statuses(int *rc) { return ret; } +void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE_WrongCredentials(PtrSize_t *pkt, bool *wrong_pass) { + l_free(pkt->size,pkt->ptr); + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Wrong credentials for frontend: disconnecting\n", this, client_myds); + *wrong_pass=true; + // FIXME: this should become close connection + client_myds->setDSS_STATE_QUERY_SENT_NET(); + char *client_addr=NULL; + if (client_myds->client_addr && client_myds->myconn->userinfo->username) { + char buf[512]; + switch (client_myds->client_addr->sa_family) { + case AF_INET: { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)client_myds->client_addr; + if (ipv4->sin_port) { + inet_ntop(client_myds->client_addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN); + client_addr = strdup(buf); + } else { + client_addr = strdup((char *)"localhost"); + } + break; + } + case AF_INET6: { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)client_myds->client_addr; + inet_ntop(client_myds->client_addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN); + client_addr = strdup(buf); + break; + } + default: + client_addr = strdup((char *)"localhost"); + break; + } + } else { + client_addr = strdup((char *)""); + } + if (client_myds->myconn->userinfo->username) { + char *_s=(char *)malloc(strlen(client_myds->myconn->userinfo->username)+100+strlen(client_addr)); + //uint8_t _pid = 2; + //if (client_myds->switching_auth_stage) _pid+=2; + //if (is_encrypted) _pid++; + uint8_t _pid = client_myds->pkt_sid; _pid++; +#ifdef DEBUG + if (client_myds->myconn->userinfo->password) { + char *tmp_pass=strdup(client_myds->myconn->userinfo->password); + int lpass = strlen(tmp_pass); + for (int i=2; imyconn->userinfo->username, client_addr, tmp_pass); + free(tmp_pass); + } else { + proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Error: Access denied for user '%s'@'%s' . No password. Disconnecting\n", this, client_myds, client_myds->myconn->userinfo->username, client_addr); + } +#endif // DEBUG + sprintf(_s,"ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO")); + client_myds->myprot.generate_pkt_ERR(true,NULL,NULL, _pid, 1045,(char *)"28000", _s, true); + proxy_error("ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)\n", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO")); + free(_s); + __sync_fetch_and_add(&MyHGM->status.access_denied_wrong_password, 1); + } + if (client_addr) { + free(client_addr); + } + GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL); + __sync_add_and_fetch(&MyHGM->status.client_connections_aborted,1); + client_myds->DSS=STATE_SLEEP; +} + void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE(PtrSize_t *pkt, bool *wrong_pass) { bool is_encrypted = client_myds->encrypted; bool handshake_response_return = client_myds->myprot.process_pkt_handshake_response((unsigned char *)pkt->ptr,pkt->size); @@ -6000,71 +6033,8 @@ void MySQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( } } } else { - l_free(pkt->size,pkt->ptr); - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Wrong credentials for frontend: disconnecting\n", this, client_myds); - *wrong_pass=true; - // FIXME: this should become close connection - client_myds->setDSS_STATE_QUERY_SENT_NET(); - char *client_addr=NULL; - if (client_myds->client_addr && client_myds->myconn->userinfo->username) { - char buf[512]; - switch (client_myds->client_addr->sa_family) { - case AF_INET: { - struct sockaddr_in *ipv4 = (struct sockaddr_in *)client_myds->client_addr; - if (ipv4->sin_port) { - inet_ntop(client_myds->client_addr->sa_family, &ipv4->sin_addr, buf, INET_ADDRSTRLEN); - client_addr = strdup(buf); - } else { - client_addr = strdup((char *)"localhost"); - } - break; - } - case AF_INET6: { - struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)client_myds->client_addr; - inet_ntop(client_myds->client_addr->sa_family, &ipv6->sin6_addr, buf, INET6_ADDRSTRLEN); - client_addr = strdup(buf); - break; - } - default: - client_addr = strdup((char *)"localhost"); - break; - } - } else { - client_addr = strdup((char *)""); - } - if (client_myds->myconn->userinfo->username) { - char *_s=(char *)malloc(strlen(client_myds->myconn->userinfo->username)+100+strlen(client_addr)); - //uint8_t _pid = 2; - //if (client_myds->switching_auth_stage) _pid+=2; - //if (is_encrypted) _pid++; - uint8_t _pid = client_myds->pkt_sid; _pid++; -#ifdef DEBUG - if (client_myds->myconn->userinfo->password) { - char *tmp_pass=strdup(client_myds->myconn->userinfo->password); - int lpass = strlen(tmp_pass); - for (int i=2; imyconn->userinfo->username, client_addr, tmp_pass); - free(tmp_pass); - } else { - proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p . Error: Access denied for user '%s'@'%s' . No password. Disconnecting\n", this, client_myds, client_myds->myconn->userinfo->username, client_addr); - } -#endif // DEBUG - sprintf(_s,"ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO")); - client_myds->myprot.generate_pkt_ERR(true,NULL,NULL, _pid, 1045,(char *)"28000", _s, true); - proxy_error("ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)\n", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO")); - free(_s); - __sync_fetch_and_add(&MyHGM->status.access_denied_wrong_password, 1); - } - if (client_addr) { - free(client_addr); - } - GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL); - __sync_add_and_fetch(&MyHGM->status.client_connections_aborted,1); - client_myds->DSS=STATE_SLEEP; + handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE_WrongCredentials(pkt, wrong_pass); } - if (mysql_thread___client_host_cache_size) { GloMTH->update_client_host_cache(client_myds->client_addr, handshake_err); } @@ -7267,6 +7237,8 @@ bool MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C if ( (pkt->size==SELECT_LAST_INSERT_ID_LEN+5 && *((char *)(pkt->ptr)+4)==(char)0x03 && strncasecmp((char *)SELECT_LAST_INSERT_ID,(char *)pkt->ptr+5,pkt->size-5)==0) || + (pkt->size==SELECT_LAST_INSERT_ID_FROM_DUAL_LEN+5 && *((char *)(pkt->ptr)+4)==(char)0x03 && strncasecmp((char *)SELECT_LAST_INSERT_ID_FROM_DUAL,(char *)pkt->ptr+5,pkt->size-5)==0) + || (pkt->size==SELECT_LAST_INSERT_ID_LIMIT1_LEN+5 && *((char *)(pkt->ptr)+4)==(char)0x03 && strncasecmp((char *)SELECT_LAST_INSERT_ID_LIMIT1,(char *)pkt->ptr+5,pkt->size-5)==0) || (pkt->size==SELECT_VARIABLE_IDENTITY_LEN+5 && *((char *)(pkt->ptr)+4)==(char)0x03 && strncasecmp((char *)SELECT_VARIABLE_IDENTITY,(char *)pkt->ptr+5,pkt->size-5)==0) diff --git a/lib/MySQL_Thread.cpp b/lib/MySQL_Thread.cpp index 167b2aa733..8d2d3e7e21 100644 --- a/lib/MySQL_Thread.cpp +++ b/lib/MySQL_Thread.cpp @@ -38,6 +38,70 @@ MySQL_Session *sess_stopat; #define PROXYSQL_LISTEN_LEN 1024 #define MIN_THREADS_FOR_MAINTENANCE 8 +/** + * @brief Helper macro to stringify a macro argument. + * + * This macro takes a single argument 'x' and converts it into a string literal. + * It is a helper macro used by the STRINGIFY macro. + * + * @param x The macro argument to be converted into a string. + * @return The string representation of the macro argument. + */ +#define STRINGIFY_HELPER(x) #x + +/** + * @brief Macro to stringify a macro argument. + * + * This macro takes a single argument 'x' and converts it into a string literal + * using the STRINGIFY_HELPER macro. + * + * @param x The macro argument to be converted into a string. + * @return The string representation of the macro argument. + */ +#define STRINGIFY(x) STRINGIFY_HELPER(x) + +/** + * @brief Refreshes a boolean variable from the MySQL thread. + * + * This macro updates the value of a boolean variable named 'name' from + * the MySQL thread. It retrieves the value using the 'get_variable_int' function + * from the 'GloMTH' object and assigns it to the variable 'mysql_thread___name'. + * The retrieved integer value is cast to a boolean before assignment. + * + * @param name The name of the boolean variable to be refreshed. + */ +#define REFRESH_VARIABLE_BOOL(name) \ + mysql_thread___ ## name = (bool)GloMTH->get_variable_int((char *)STRINGIFY(name)) + +/** + * @brief Refreshes an integer variable from the MySQL thread. + * + * This macro updates the value of an integer variable named 'name' from + * the MySQL thread. It retrieves the value using the 'get_variable_int' function + * from the 'GloMTH' object and assigns it to the variable 'mysql_thread___name'. + * + * @param name The name of the integer variable to be refreshed. + */ +#define REFRESH_VARIABLE_INT(name) \ + mysql_thread___ ## name = GloMTH->get_variable_int((char *)STRINGIFY(name)) + +/** + * @brief Refreshes a character variable from the MySQL thread. + * + * This macro updates the value of a character variable named 'name' from + * the MySQL thread. It retrieves the value using the 'get_variable_string' function + * from the 'GloMTH' object and assigns it to the variable 'mysql_thread___name'. + * If the variable 'mysql_thread___name' was previously allocated memory, + * it frees that memory before assigning the new value. + * + * @param name The name of the character variable to be refreshed. + */ +#define REFRESH_VARIABLE_CHAR(name) \ + do { \ + if (mysql_thread___ ## name) free(mysql_thread___ ## name); \ + mysql_thread___ ## name = GloMTH->get_variable_string((char *)STRINGIFY(name)); \ + } while (0) + extern Query_Processor *GloQPro; extern MySQL_Authentication *GloMyAuth; extern MySQL_Threads_Handler *GloMTH; @@ -3142,6 +3206,134 @@ void MySQL_Thread::run___cleanup_mirror_queue() { } } + +#ifdef IDLE_THREADS +/** + * @brief Handles session assignment and retrieval between worker and idle threads. + * + * This block of code checks if the global configuration allows idle threads and if the current thread + * is not an idle maintenance thread. If both conditions are met, it randomly selects an idle worker thread + * and assigns sessions to it. Then, it retrieves sessions from the idle thread. + * + * @note This functionality is part of the management of worker and idle threads in the MySQL thread pool. + * It facilitates the distribution of sessions between active worker threads and idle threads to optimize resource utilization. + * + * @param idle_maintenance_thread Flag indicating whether the current thread is an idle maintenance thread. + * @param GloVars Global configuration variables for the MySQL thread. + * @param GloMTH Global MySQL thread handlers object. + */ +void MySQL_Thread::run_MoveSessionsBetweenThreads() { + if (GloVars.global.idle_threads) { + int r=rand()%(GloMTH->num_threads); + MySQL_Thread *thr=GloMTH->mysql_threads_idles[r].worker; + worker_thread_assigns_sessions_to_idle_thread(thr); + worker_thread_gets_sessions_from_idle_thread(); + } +} + +void MySQL_Thread::run_Handle_epoll_wait(int rc) { + if (rc) { + int i; + for (i=0; ilen && maintenance_loop) { + if (curtime == last_maintenance_time) { + idle_thread_to_kill_idle_sessions(); + } + } +} +#endif // IDLE_THREADS + + +void MySQL_Thread::run_BootstrapListener() { + unsigned int n; + while ( // spin here if ... + (n=__sync_add_and_fetch(&mypolls.pending_listener_add,0)) // there is a new listener to add + || + (GloMTH->bootstrapping_listeners == true) // MySQL_Thread_Handlers has more listeners to configure + ) { + if (n) { + poll_listener_add(n); + assert(__sync_bool_compare_and_swap(&mypolls.pending_listener_add,n,0)); + } else { + if (GloMTH->bootstrapping_listeners == false) { + // we stop looping + mypolls.bootstrapping_listeners = false; + } + } +#ifdef DEBUG + usleep(5+rand()%10); +#endif + } +} + +int MySQL_Thread::run_ComputePollTimeout() { + proxy_debug(PROXY_DEBUG_NET, 7, "poll_timeout=%u\n", mypolls.poll_timeout); + if (mysql_thread___wait_timeout==0) { + // we should be going into PAUSE mode + if (mypolls.poll_timeout==0 || mypolls.poll_timeout > 100000) { + mypolls.poll_timeout=100000; + } + } + proxy_debug(PROXY_DEBUG_NET, 7, "poll_timeout=%u\n", mypolls.poll_timeout); + + + pre_poll_time=curtime; + int ttw = ( mypolls.poll_timeout ? ( mypolls.poll_timeout/1000 < (unsigned int) mysql_thread___poll_timeout ? mypolls.poll_timeout/1000 : mysql_thread___poll_timeout ) : mysql_thread___poll_timeout ); + return ttw; +} + + +void MySQL_Thread::run_StopListener() { + unsigned int n; + while ((n=__sync_add_and_fetch(&mypolls.pending_listener_del,0))) { // spin here + if (static_cast(n) == -1) { + for (unsigned int i = 0; i < mypolls.len; i++) { + if (mypolls.myds[i] && mypolls.myds[i]->myds_type == MYDS_LISTENER) { + poll_listener_del(mypolls.myds[i]->fd); + } + } + } else { + poll_listener_del(n); + } + assert(__sync_bool_compare_and_swap(&mypolls.pending_listener_del,n,0)); + } +} + + +void MySQL_Thread::run_SetAllSession_ToProcess0() { + unsigned int n; +#ifdef IDLE_THREADS + // @note: in MySQL_Thread::run we have: bool idle_maintenance_thread=epoll_thread; + // Thus idle_maintenance_thread and epoll_thread are equivalent. + if (epoll_thread==false) { +#endif // IDLE_THREADS + for (n=0; nlen; n++) { + MySQL_Session *_sess=(MySQL_Session *)mysql_sessions->index(n); + _sess->to_process=0; + } +#ifdef IDLE_THREADS + } +#endif // IDLE_THREADS +} + + // main loop /** * @brief Main loop for the MySQL thread. @@ -3152,7 +3344,6 @@ void MySQL_Thread::run___cleanup_mirror_queue() { * @note This method assumes that relevant variables, mutexes, and objects have been properly initialized. */ void MySQL_Thread::run() { - unsigned int n; int rc; #ifdef IDLE_THREADS @@ -3196,79 +3387,28 @@ void MySQL_Thread::run() { if (idle_maintenance_thread) { idle_thread_gets_sessions_from_worker_thread(); - goto __run_skip_1a; - } + } else { #endif // IDLE_THREADS - handle_mirror_queue_mysql_sessions(); + handle_mirror_queue_mysql_sessions(); - ProcessAllMyDS_BeforePoll(); + ProcessAllMyDS_BeforePoll(); #ifdef IDLE_THREADS - /** - * @brief Handles session assignment and retrieval between worker and idle threads. - * - * This block of code checks if the global configuration allows idle threads and if the current thread - * is not an idle maintenance thread. If both conditions are met, it randomly selects an idle worker thread - * and assigns sessions to it. Then, it retrieves sessions from the idle thread. - * - * @note This functionality is part of the management of worker and idle threads in the MySQL thread pool. - * It facilitates the distribution of sessions between active worker threads and idle threads to optimize resource utilization. - * - * @param idle_maintenance_thread Flag indicating whether the current thread is an idle maintenance thread. - * @param GloVars Global configuration variables for the MySQL thread. - * @param GloMTH Global MySQL thread handlers object. - */ - if (GloVars.global.idle_threads) { - if (idle_maintenance_thread==false) { - int r=rand()%(GloMTH->num_threads); - MySQL_Thread *thr=GloMTH->mysql_threads_idles[r].worker; - worker_thread_assigns_sessions_to_idle_thread(thr); - worker_thread_gets_sessions_from_idle_thread(); - } + run_MoveSessionsBetweenThreads(); } - - -__run_skip_1a: #endif // IDLE_THREADS pthread_mutex_unlock(&thread_mutex); if (unlikely(mypolls.bootstrapping_listeners == true)) { - while ( // spin here if ... - (n=__sync_add_and_fetch(&mypolls.pending_listener_add,0)) // there is a new listener to add - || - (GloMTH->bootstrapping_listeners == true) // MySQL_Thread_Handlers has more listeners to configure - ) { - if (n) { - poll_listener_add(n); - assert(__sync_bool_compare_and_swap(&mypolls.pending_listener_add,n,0)); - } else { - if (GloMTH->bootstrapping_listeners == false) { - // we stop looping - mypolls.bootstrapping_listeners = false; - } - } -#ifdef DEBUG - usleep(5+rand()%10); -#endif - } - } - - proxy_debug(PROXY_DEBUG_NET, 7, "poll_timeout=%u\n", mypolls.poll_timeout); - if (mysql_thread___wait_timeout==0) { - // we should be going into PAUSE mode - if (mypolls.poll_timeout==0 || mypolls.poll_timeout > 100000) { - mypolls.poll_timeout=100000; - } + run_BootstrapListener(); } - proxy_debug(PROXY_DEBUG_NET, 7, "poll_timeout=%u\n", mypolls.poll_timeout); - // flush mysql log file GloMyLogger->flush(); - pre_poll_time=curtime; - int ttw = ( mypolls.poll_timeout ? ( mypolls.poll_timeout/1000 < (unsigned int) mysql_thread___poll_timeout ? mypolls.poll_timeout/1000 : mysql_thread___poll_timeout ) : mysql_thread___poll_timeout ); + int ttw = run_ComputePollTimeout(); + #ifdef IDLE_THREADS if (GloVars.global.idle_threads && idle_maintenance_thread) { memset(events,0,sizeof(struct epoll_event)*MY_EPOLL_THREAD_MAXEVENTS); // let's make valgrind happy. It also seems that needs to be zeroed anyway @@ -3286,17 +3426,8 @@ void MySQL_Thread::run() { #endif // IDLE_THREADS if (unlikely(maintenance_loop == true)) { - while ((n=__sync_add_and_fetch(&mypolls.pending_listener_del,0))) { // spin here - if (static_cast(n) == -1) { - for (unsigned int i = 0; i < mypolls.len; i++) { - if (mypolls.myds[i] && mypolls.myds[i]->myds_type == MYDS_LISTENER) { - poll_listener_del(mypolls.myds[i]->fd); - } - } - } else { - poll_listener_del(n); - } - assert(__sync_bool_compare_and_swap(&mypolls.pending_listener_del,n,0)); + if (unlikely(__sync_add_and_fetch(&mypolls.pending_listener_del,0))) { + run_StopListener(); } } @@ -3366,55 +3497,12 @@ void MySQL_Thread::run() { refresh_variables(); } -#ifdef IDLE_THREADS - if (idle_maintenance_thread==false) { -#endif // IDLE_THREADS - for (n=0; nlen; n++) { - MySQL_Session *_sess=(MySQL_Session *)mysql_sessions->index(n); - _sess->to_process=0; - } -#ifdef IDLE_THREADS - } -#endif // IDLE_THREADS + run_SetAllSession_ToProcess0(); #ifdef IDLE_THREADS // here we handle epoll_wait() if (GloVars.global.idle_threads && idle_maintenance_thread) { - if (rc) { - int i; - for (i=0; ilen && maintenance_loop) { - if (curtime == last_maintenance_time) { - idle_thread_to_kill_idle_sessions(); - } - } - goto __run_skip_2; - } -#endif // IDLE_THREADS - - ProcessAllMyDS_AfterPoll(); - -#ifdef IDLE_THREADS -__run_skip_2: - if (GloVars.global.idle_threads && idle_maintenance_thread) { - // this is an idle thread + run_Handle_epoll_wait(rc); unsigned int w=rand()%(GloMTH->num_threads); MySQL_Thread *thr=GloMTH->mysql_threads[w].worker; if (resume_mysql_sessions->len) { @@ -3424,13 +3512,14 @@ void MySQL_Thread::run() { } } else { #endif // IDLE_THREADS + ProcessAllMyDS_AfterPoll(); // iterate through all sessions and process the session logic process_all_sessions(); - return_local_connections(); #ifdef IDLE_THREADS } #endif // IDLE_THREADS + } } // end of ::run() @@ -3984,6 +4073,30 @@ void MySQL_Thread::ProcessAllSessions_MaintenanceLoop(MySQL_Session *sess, unsig } +void MySQL_Thread::ProcessAllSessions_Healthy0(MySQL_Session *sess, unsigned int& n) { + char _buf[1024]; + if (sess->client_myds) { + if (mysql_thread___log_unhealthy_connections) { + if (sess->session_fast_forward == false) { + proxy_warning( + "Closing unhealthy client connection %s:%d\n", sess->client_myds->addr.addr, + sess->client_myds->addr.port + ); + } else { + proxy_warning( + "Closing 'fast_forward' client connection %s:%d\n", sess->client_myds->addr.addr, + sess->client_myds->addr.port + ); + } + } + } + sprintf(_buf,"%s:%d:%s()", __FILE__, __LINE__, __func__); + GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_CLOSE, sess, NULL, _buf); + unregister_session(n); + n--; + delete sess; +} + /** * @brief Processes all active sessions within the MySQL thread. * @@ -4073,28 +4186,8 @@ void MySQL_Thread::process_all_sessions() { // removing this logic in 2.0.15 //sess->active_transactions = -1; } - if (sess->healthy==0) { - char _buf[1024]; - if (sess->client_myds) { - if (mysql_thread___log_unhealthy_connections) { - if (sess->session_fast_forward == false) { - proxy_warning( - "Closing unhealthy client connection %s:%d\n", sess->client_myds->addr.addr, - sess->client_myds->addr.port - ); - } else { - proxy_warning( - "Closing 'fast_forward' client connection %s:%d\n", sess->client_myds->addr.addr, - sess->client_myds->addr.port - ); - } - } - } - sprintf(_buf,"%s:%d:%s()", __FILE__, __LINE__, __func__); - GloMyLogger->log_audit_entry(PROXYSQL_MYSQL_AUTH_CLOSE, sess, NULL, _buf); - unregister_session(n); - n--; - delete sess; + if (unlikely(sess->healthy==0)) { + ProcessAllSessions_Healthy0(sess, n); } else { if (sess->to_process==1) { if (sess->pause_until <= curtime) { @@ -4112,7 +4205,7 @@ void MySQL_Thread::process_all_sessions() { } } } else { - if (sess->killed==true) { + if (unlikely(sess->killed==true)) { // this is a special cause, if killed the session needs to be executed no matter if paused sess->handler(); char _buf[1024]; @@ -4155,126 +4248,111 @@ void MySQL_Thread::refresh_variables() { } GloMTH->wrlock(); __thread_MySQL_Thread_Variables_version=__global_MySQL_Thread_Variables_version; - mysql_thread___max_allowed_packet=GloMTH->get_variable_int((char *)"max_allowed_packet"); - mysql_thread___automatic_detect_sqli=(bool)GloMTH->get_variable_int((char *)"automatic_detect_sqli"); - mysql_thread___firewall_whitelist_enabled=(bool)GloMTH->get_variable_int((char *)"firewall_whitelist_enabled"); - mysql_thread___use_tcp_keepalive=(bool)GloMTH->get_variable_int((char *)"use_tcp_keepalive"); - mysql_thread___tcp_keepalive_time=GloMTH->get_variable_int((char *)"tcp_keepalive_time"); - mysql_thread___throttle_connections_per_sec_to_hostgroup=GloMTH->get_variable_int((char *)"throttle_connections_per_sec_to_hostgroup"); - mysql_thread___max_transaction_idle_time=GloMTH->get_variable_int((char *)"max_transaction_idle_time"); - mysql_thread___max_transaction_time=GloMTH->get_variable_int((char *)"max_transaction_time"); - mysql_thread___threshold_query_length=GloMTH->get_variable_int((char *)"threshold_query_length"); - mysql_thread___threshold_resultset_size=GloMTH->get_variable_int((char *)"threshold_resultset_size"); - mysql_thread___query_digests_max_digest_length=GloMTH->get_variable_int((char *)"query_digests_max_digest_length"); - mysql_thread___query_digests_max_query_length=GloMTH->get_variable_int((char *)"query_digests_max_query_length"); - mysql_thread___wait_timeout=GloMTH->get_variable_int((char *)"wait_timeout"); - mysql_thread___throttle_max_bytes_per_second_to_client=GloMTH->get_variable_int((char *)"throttle_max_bytes_per_second_to_client"); - mysql_thread___throttle_ratio_server_to_client=GloMTH->get_variable_int((char *)"throttle_ratio_server_to_client"); - mysql_thread___max_connections=GloMTH->get_variable_int((char *)"max_connections"); - mysql_thread___max_stmts_per_connection=GloMTH->get_variable_int((char *)"max_stmts_per_connection"); - mysql_thread___max_stmts_cache=GloMTH->get_variable_int((char *)"max_stmts_cache"); - mysql_thread___mirror_max_concurrency=GloMTH->get_variable_int((char *)"mirror_max_concurrency"); - mysql_thread___mirror_max_queue_length=GloMTH->get_variable_int((char *)"mirror_max_queue_length"); - mysql_thread___default_query_delay=GloMTH->get_variable_int((char *)"default_query_delay"); - mysql_thread___default_query_timeout=GloMTH->get_variable_int((char *)"default_query_timeout"); - mysql_thread___query_processor_iterations=GloMTH->get_variable_int((char *)"query_processor_iterations"); - mysql_thread___query_processor_regex=GloMTH->get_variable_int((char *)"query_processor_regex"); - mysql_thread___set_query_lock_on_hostgroup=GloMTH->get_variable_int((char *)"set_query_lock_on_hostgroup"); - mysql_thread___set_parser_algorithm=GloMTH->get_variable_int((char *)"set_parser_algorithm"); - mysql_thread___reset_connection_algorithm=GloMTH->get_variable_int((char *)"reset_connection_algorithm"); - mysql_thread___auto_increment_delay_multiplex=GloMTH->get_variable_int((char *)"auto_increment_delay_multiplex"); - mysql_thread___auto_increment_delay_multiplex_timeout_ms=GloMTH->get_variable_int((char *)"auto_increment_delay_multiplex_timeout_ms"); - mysql_thread___default_max_latency_ms=GloMTH->get_variable_int((char *)"default_max_latency_ms"); - mysql_thread___long_query_time=GloMTH->get_variable_int((char *)"long_query_time"); - mysql_thread___query_cache_size_MB=GloMTH->get_variable_int((char *)"query_cache_size_MB"); - mysql_thread___query_cache_soft_ttl_pct=GloMTH->get_variable_int((char *)"query_cache_soft_ttl_pct"); - mysql_thread___query_cache_handle_warnings =GloMTH->get_variable_int((char*)"query_cache_handle_warnings"); - mysql_thread___ping_interval_server_msec=GloMTH->get_variable_int((char *)"ping_interval_server_msec"); - mysql_thread___ping_timeout_server=GloMTH->get_variable_int((char *)"ping_timeout_server"); - mysql_thread___shun_on_failures=GloMTH->get_variable_int((char *)"shun_on_failures"); - mysql_thread___shun_recovery_time_sec=GloMTH->get_variable_int((char *)"shun_recovery_time_sec"); - mysql_thread___unshun_algorithm=GloMTH->get_variable_int((char *)"unshun_algorithm"); - mysql_thread___query_retries_on_failure=GloMTH->get_variable_int((char *)"query_retries_on_failure"); - mysql_thread___connect_retries_on_failure=GloMTH->get_variable_int((char *)"connect_retries_on_failure"); - mysql_thread___connection_delay_multiplex_ms=GloMTH->get_variable_int((char *)"connection_delay_multiplex_ms"); - mysql_thread___connection_max_age_ms=GloMTH->get_variable_int((char *)"connection_max_age_ms"); - mysql_thread___connect_timeout_client=GloMTH->get_variable_int((char *)"connect_timeout_client"); - mysql_thread___connect_timeout_server=GloMTH->get_variable_int((char *)"connect_timeout_server"); - mysql_thread___connect_timeout_server_max=GloMTH->get_variable_int((char *)"connect_timeout_server_max"); - mysql_thread___free_connections_pct=GloMTH->get_variable_int((char *)"free_connections_pct"); + REFRESH_VARIABLE_INT (max_allowed_packet); + REFRESH_VARIABLE_BOOL(automatic_detect_sqli); + REFRESH_VARIABLE_BOOL(firewall_whitelist_enabled); + REFRESH_VARIABLE_BOOL(use_tcp_keepalive); + REFRESH_VARIABLE_INT(tcp_keepalive_time); + REFRESH_VARIABLE_INT(throttle_connections_per_sec_to_hostgroup); + REFRESH_VARIABLE_INT(max_transaction_idle_time); + REFRESH_VARIABLE_INT(max_transaction_time); + REFRESH_VARIABLE_INT(threshold_query_length); + REFRESH_VARIABLE_INT(threshold_resultset_size); + REFRESH_VARIABLE_INT(query_digests_max_digest_length); + REFRESH_VARIABLE_INT(query_digests_max_query_length); + REFRESH_VARIABLE_INT(wait_timeout); + REFRESH_VARIABLE_INT(throttle_max_bytes_per_second_to_client); + REFRESH_VARIABLE_INT(throttle_ratio_server_to_client); + REFRESH_VARIABLE_INT(max_connections); + REFRESH_VARIABLE_INT(max_stmts_per_connection); + REFRESH_VARIABLE_INT(max_stmts_cache); + REFRESH_VARIABLE_INT(mirror_max_concurrency); + REFRESH_VARIABLE_INT(mirror_max_queue_length); + REFRESH_VARIABLE_INT(default_query_delay); + REFRESH_VARIABLE_INT(default_query_timeout); + REFRESH_VARIABLE_INT(query_processor_iterations); + REFRESH_VARIABLE_INT(query_processor_regex); + REFRESH_VARIABLE_INT(set_query_lock_on_hostgroup); + REFRESH_VARIABLE_INT(set_parser_algorithm); + REFRESH_VARIABLE_INT(reset_connection_algorithm); + REFRESH_VARIABLE_INT(auto_increment_delay_multiplex); + REFRESH_VARIABLE_INT(auto_increment_delay_multiplex_timeout_ms); + REFRESH_VARIABLE_INT(default_max_latency_ms); + REFRESH_VARIABLE_INT(long_query_time); + REFRESH_VARIABLE_INT(query_cache_size_MB); + REFRESH_VARIABLE_INT(query_cache_soft_ttl_pct); + REFRESH_VARIABLE_INT(query_cache_handle_warnings); + REFRESH_VARIABLE_INT(ping_interval_server_msec); + REFRESH_VARIABLE_INT(ping_timeout_server); + REFRESH_VARIABLE_INT(shun_on_failures); + REFRESH_VARIABLE_INT(shun_recovery_time_sec); + REFRESH_VARIABLE_INT(unshun_algorithm); + REFRESH_VARIABLE_INT(query_retries_on_failure); + REFRESH_VARIABLE_INT(connect_retries_on_failure); + REFRESH_VARIABLE_INT(connection_delay_multiplex_ms); + REFRESH_VARIABLE_INT(connection_max_age_ms); + REFRESH_VARIABLE_INT(connect_timeout_client); + REFRESH_VARIABLE_INT(connect_timeout_server); + REFRESH_VARIABLE_INT(connect_timeout_server_max); + REFRESH_VARIABLE_INT(free_connections_pct); #ifdef IDLE_THREADS - mysql_thread___session_idle_ms=GloMTH->get_variable_int((char *)"session_idle_ms"); + REFRESH_VARIABLE_INT(session_idle_ms); #endif // IDLE_THREADS - mysql_thread___connect_retries_delay=GloMTH->get_variable_int((char *)"connect_retries_delay"); + REFRESH_VARIABLE_INT(connect_retries_delay); - if (mysql_thread___monitor_username) free(mysql_thread___monitor_username); - mysql_thread___monitor_username=GloMTH->get_variable_string((char *)"monitor_username"); - if (mysql_thread___monitor_password) free(mysql_thread___monitor_password); - mysql_thread___monitor_password=GloMTH->get_variable_string((char *)"monitor_password"); - if (mysql_thread___monitor_replication_lag_use_percona_heartbeat) free(mysql_thread___monitor_replication_lag_use_percona_heartbeat); - mysql_thread___monitor_replication_lag_use_percona_heartbeat=GloMTH->get_variable_string((char *)"monitor_replication_lag_use_percona_heartbeat"); + REFRESH_VARIABLE_CHAR(monitor_username); + REFRESH_VARIABLE_CHAR(monitor_password); + REFRESH_VARIABLE_CHAR(monitor_replication_lag_use_percona_heartbeat); // SSL proxy to server - if (mysql_thread___ssl_p2s_ca) free(mysql_thread___ssl_p2s_ca); - mysql_thread___ssl_p2s_ca=GloMTH->get_variable_string((char *)"ssl_p2s_ca"); - if (mysql_thread___ssl_p2s_capath) free(mysql_thread___ssl_p2s_capath); - mysql_thread___ssl_p2s_capath=GloMTH->get_variable_string((char *)"ssl_p2s_capath"); - if (mysql_thread___ssl_p2s_cert) free(mysql_thread___ssl_p2s_cert); - mysql_thread___ssl_p2s_cert=GloMTH->get_variable_string((char *)"ssl_p2s_cert"); - if (mysql_thread___ssl_p2s_key) free(mysql_thread___ssl_p2s_key); - mysql_thread___ssl_p2s_key=GloMTH->get_variable_string((char *)"ssl_p2s_key"); - if (mysql_thread___ssl_p2s_cipher) free(mysql_thread___ssl_p2s_cipher); - mysql_thread___ssl_p2s_cipher=GloMTH->get_variable_string((char *)"ssl_p2s_cipher"); - if (mysql_thread___ssl_p2s_crl) free(mysql_thread___ssl_p2s_crl); - mysql_thread___ssl_p2s_crl=GloMTH->get_variable_string((char *)"ssl_p2s_crl"); - if (mysql_thread___ssl_p2s_crlpath) free(mysql_thread___ssl_p2s_crlpath); - mysql_thread___ssl_p2s_crlpath=GloMTH->get_variable_string((char *)"ssl_p2s_crlpath"); - - mysql_thread___monitor_wait_timeout=(bool)GloMTH->get_variable_int((char *)"monitor_wait_timeout"); - mysql_thread___monitor_writer_is_also_reader=(bool)GloMTH->get_variable_int((char *)"monitor_writer_is_also_reader"); - mysql_thread___monitor_enabled=(bool)GloMTH->get_variable_int((char *)"monitor_enabled"); - mysql_thread___monitor_history=GloMTH->get_variable_int((char *)"monitor_history"); - mysql_thread___monitor_connect_interval=GloMTH->get_variable_int((char *)"monitor_connect_interval"); - mysql_thread___monitor_connect_timeout=GloMTH->get_variable_int((char *)"monitor_connect_timeout"); - mysql_thread___monitor_ping_interval=GloMTH->get_variable_int((char *)"monitor_ping_interval"); - mysql_thread___monitor_ping_max_failures=GloMTH->get_variable_int((char *)"monitor_ping_max_failures"); - mysql_thread___monitor_ping_timeout=GloMTH->get_variable_int((char *)"monitor_ping_timeout"); - mysql_thread___monitor_aws_rds_topology_discovery_interval=GloMTH->get_variable_int((char *)"monitor_aws_rds_topology_discovery_interval"); - mysql_thread___monitor_read_only_interval=GloMTH->get_variable_int((char *)"monitor_read_only_interval"); - mysql_thread___monitor_read_only_timeout=GloMTH->get_variable_int((char *)"monitor_read_only_timeout"); - mysql_thread___monitor_read_only_max_timeout_count=GloMTH->get_variable_int((char *)"monitor_read_only_max_timeout_count"); - mysql_thread___monitor_replication_lag_group_by_host=(bool)GloMTH->get_variable_int((char *)"monitor_replication_lag_group_by_host"); - mysql_thread___monitor_replication_lag_interval=GloMTH->get_variable_int((char *)"monitor_replication_lag_interval"); - mysql_thread___monitor_replication_lag_timeout=GloMTH->get_variable_int((char *)"monitor_replication_lag_timeout"); - mysql_thread___monitor_replication_lag_count=GloMTH->get_variable_int((char *)"monitor_replication_lag_count"); - mysql_thread___monitor_groupreplication_healthcheck_interval=GloMTH->get_variable_int((char *)"monitor_groupreplication_healthcheck_interval"); - mysql_thread___monitor_groupreplication_healthcheck_timeout=GloMTH->get_variable_int((char *)"monitor_groupreplication_healthcheck_timeout"); - mysql_thread___monitor_groupreplication_healthcheck_max_timeout_count=GloMTH->get_variable_int((char *)"monitor_groupreplication_healthcheck_max_timeout_count"); - mysql_thread___monitor_groupreplication_max_transactions_behind_count=GloMTH->get_variable_int((char *)"monitor_groupreplication_max_transactions_behind_count"); - mysql_thread___monitor_groupreplication_max_transaction_behind_for_read_only=GloMTH->get_variable_int((char *)"monitor_groupreplication_max_transactions_behind_for_read_only"); - mysql_thread___monitor_galera_healthcheck_interval=GloMTH->get_variable_int((char *)"monitor_galera_healthcheck_interval"); - mysql_thread___monitor_galera_healthcheck_timeout=GloMTH->get_variable_int((char *)"monitor_galera_healthcheck_timeout"); - mysql_thread___monitor_galera_healthcheck_max_timeout_count=GloMTH->get_variable_int((char *)"monitor_galera_healthcheck_max_timeout_count"); - mysql_thread___monitor_query_interval=GloMTH->get_variable_int((char *)"monitor_query_interval"); - mysql_thread___monitor_query_timeout=GloMTH->get_variable_int((char *)"monitor_query_timeout"); - mysql_thread___monitor_slave_lag_when_null=GloMTH->get_variable_int((char *)"monitor_slave_lag_when_null"); - mysql_thread___monitor_threads_min = GloMTH->get_variable_int((char *)"monitor_threads_min"); - mysql_thread___monitor_threads_max = GloMTH->get_variable_int((char *)"monitor_threads_max"); - mysql_thread___monitor_threads_queue_maxsize = GloMTH->get_variable_int((char *)"monitor_threads_queue_maxsize"); - mysql_thread___monitor_local_dns_cache_ttl = GloMTH->get_variable_int((char*)"monitor_local_dns_cache_ttl"); - mysql_thread___monitor_local_dns_cache_refresh_interval = GloMTH->get_variable_int((char*)"monitor_local_dns_cache_refresh_interval"); - mysql_thread___monitor_local_dns_resolver_queue_maxsize = GloMTH->get_variable_int((char*)"monitor_local_dns_resolver_queue_maxsize"); - - if (mysql_thread___firewall_whitelist_errormsg) free(mysql_thread___firewall_whitelist_errormsg); - mysql_thread___firewall_whitelist_errormsg=GloMTH->get_variable_string((char *)"firewall_whitelist_errormsg"); - if (mysql_thread___init_connect) free(mysql_thread___init_connect); - mysql_thread___init_connect=GloMTH->get_variable_string((char *)"init_connect"); - if (mysql_thread___ldap_user_variable) free(mysql_thread___ldap_user_variable); - mysql_thread___ldap_user_variable=GloMTH->get_variable_string((char *)"ldap_user_variable"); - if (mysql_thread___add_ldap_user_comment) free(mysql_thread___add_ldap_user_comment); - mysql_thread___add_ldap_user_comment=GloMTH->get_variable_string((char *)"add_ldap_user_comment"); - if (mysql_thread___default_session_track_gtids) free(mysql_thread___default_session_track_gtids); - mysql_thread___default_session_track_gtids=GloMTH->get_variable_string((char *)"default_session_track_gtids"); + REFRESH_VARIABLE_CHAR(ssl_p2s_ca); + REFRESH_VARIABLE_CHAR(ssl_p2s_capath); + REFRESH_VARIABLE_CHAR(ssl_p2s_cert); + REFRESH_VARIABLE_CHAR(ssl_p2s_key); + REFRESH_VARIABLE_CHAR(ssl_p2s_cipher); + REFRESH_VARIABLE_CHAR(ssl_p2s_crl); + REFRESH_VARIABLE_CHAR(ssl_p2s_crlpath); + + REFRESH_VARIABLE_BOOL(monitor_wait_timeout); + REFRESH_VARIABLE_BOOL(monitor_writer_is_also_reader); + REFRESH_VARIABLE_BOOL(monitor_enabled); + REFRESH_VARIABLE_INT(monitor_history); + REFRESH_VARIABLE_INT(monitor_connect_interval); + REFRESH_VARIABLE_INT(monitor_connect_timeout); + REFRESH_VARIABLE_INT(monitor_ping_interval); + REFRESH_VARIABLE_INT(monitor_ping_max_failures); + REFRESH_VARIABLE_INT(monitor_ping_timeout); + REFRESH_VARIABLE_INT(monitor_aws_rds_topology_discovery_interval); + REFRESH_VARIABLE_INT(monitor_read_only_interval); + REFRESH_VARIABLE_INT(monitor_read_only_timeout); + REFRESH_VARIABLE_INT(monitor_read_only_max_timeout_count); + REFRESH_VARIABLE_BOOL(monitor_replication_lag_group_by_host); + REFRESH_VARIABLE_INT(monitor_replication_lag_interval); + REFRESH_VARIABLE_INT(monitor_replication_lag_timeout); + REFRESH_VARIABLE_INT(monitor_replication_lag_count); + REFRESH_VARIABLE_INT(monitor_groupreplication_healthcheck_interval); + REFRESH_VARIABLE_INT(monitor_groupreplication_healthcheck_timeout); + REFRESH_VARIABLE_INT(monitor_groupreplication_healthcheck_max_timeout_count); + REFRESH_VARIABLE_INT(monitor_groupreplication_max_transactions_behind_count); + REFRESH_VARIABLE_INT(monitor_groupreplication_max_transactions_behind_for_read_only); + REFRESH_VARIABLE_INT(monitor_galera_healthcheck_interval); + REFRESH_VARIABLE_INT(monitor_galera_healthcheck_timeout); + REFRESH_VARIABLE_INT(monitor_galera_healthcheck_max_timeout_count); + REFRESH_VARIABLE_INT(monitor_query_interval); + REFRESH_VARIABLE_INT(monitor_query_timeout); + REFRESH_VARIABLE_INT(monitor_slave_lag_when_null); + REFRESH_VARIABLE_INT(monitor_threads_min); + REFRESH_VARIABLE_INT(monitor_threads_max); + REFRESH_VARIABLE_INT(monitor_threads_queue_maxsize); + REFRESH_VARIABLE_INT(monitor_local_dns_cache_ttl); + REFRESH_VARIABLE_INT(monitor_local_dns_cache_refresh_interval); + REFRESH_VARIABLE_INT(monitor_local_dns_resolver_queue_maxsize); + + REFRESH_VARIABLE_CHAR(firewall_whitelist_errormsg); + REFRESH_VARIABLE_CHAR(init_connect); + REFRESH_VARIABLE_CHAR(ldap_user_variable); + REFRESH_VARIABLE_CHAR(add_ldap_user_comment); + REFRESH_VARIABLE_CHAR(default_session_track_gtids); for (int i=0; iget_variable_string((char *)"server_version"); - if (mysql_thread___eventslog_filename) free(mysql_thread___eventslog_filename); - mysql_thread___eventslog_filesize=GloMTH->get_variable_int((char *)"eventslog_filesize"); - mysql_thread___eventslog_default_log=GloMTH->get_variable_int((char *)"eventslog_default_log"); - mysql_thread___eventslog_format=GloMTH->get_variable_int((char *)"eventslog_format"); - mysql_thread___eventslog_filename=GloMTH->get_variable_string((char *)"eventslog_filename"); - if (mysql_thread___auditlog_filename) free(mysql_thread___auditlog_filename); - mysql_thread___auditlog_filesize=GloMTH->get_variable_int((char *)"auditlog_filesize"); - mysql_thread___auditlog_filename=GloMTH->get_variable_string((char *)"auditlog_filename"); + REFRESH_VARIABLE_CHAR(server_version); + REFRESH_VARIABLE_INT(eventslog_filesize); + REFRESH_VARIABLE_INT(eventslog_default_log); + REFRESH_VARIABLE_INT(eventslog_format); + REFRESH_VARIABLE_CHAR(eventslog_filename); + REFRESH_VARIABLE_INT(auditlog_filesize); + REFRESH_VARIABLE_CHAR(auditlog_filename); GloMyLogger->events_set_base_filename(); // both filename and filesize are set here GloMyLogger->audit_set_base_filename(); // both filename and filesize are set here - if (mysql_thread___default_schema) free(mysql_thread___default_schema); - mysql_thread___default_schema=GloMTH->get_variable_string((char *)"default_schema"); - if (mysql_thread___keep_multiplexing_variables) free(mysql_thread___keep_multiplexing_variables); - mysql_thread___keep_multiplexing_variables=GloMTH->get_variable_string((char *)"keep_multiplexing_variables"); - if (mysql_thread___default_authentication_plugin) free(mysql_thread___default_authentication_plugin); - mysql_thread___default_authentication_plugin=GloMTH->get_variable_string((char *)"default_authentication_plugin"); + REFRESH_VARIABLE_CHAR(default_schema); + REFRESH_VARIABLE_CHAR(keep_multiplexing_variables); + REFRESH_VARIABLE_CHAR(default_authentication_plugin); mysql_thread___default_authentication_plugin_int = GloMTH->variables.default_authentication_plugin_int; mysql_thread___server_capabilities=GloMTH->get_variable_uint16((char *)"server_capabilities"); - mysql_thread___handle_unknown_charset=GloMTH->get_variable_int((char *)"handle_unknown_charset"); - mysql_thread___poll_timeout=GloMTH->get_variable_int((char *)"poll_timeout"); - mysql_thread___poll_timeout_on_failure=GloMTH->get_variable_int((char *)"poll_timeout_on_failure"); - mysql_thread___have_compress=(bool)GloMTH->get_variable_int((char *)"have_compress"); - mysql_thread___have_ssl=(bool)GloMTH->get_variable_int((char *)"have_ssl"); - mysql_thread___multiplexing=(bool)GloMTH->get_variable_int((char *)"multiplexing"); - mysql_thread___log_unhealthy_connections=(bool)GloMTH->get_variable_int((char *)"log_unhealthy_connections"); - mysql_thread___connection_warming=(bool)GloMTH->get_variable_int((char*)"connection_warming"); - mysql_thread___enforce_autocommit_on_reads=(bool)GloMTH->get_variable_int((char *)"enforce_autocommit_on_reads"); - mysql_thread___autocommit_false_not_reusable=(bool)GloMTH->get_variable_int((char *)"autocommit_false_not_reusable"); - mysql_thread___autocommit_false_is_transaction=(bool)GloMTH->get_variable_int((char *)"autocommit_false_is_transaction"); - mysql_thread___verbose_query_error=(bool)GloMTH->get_variable_int((char *)"verbose_query_error"); - mysql_thread___commands_stats=(bool)GloMTH->get_variable_int((char *)"commands_stats"); - mysql_thread___query_digests=(bool)GloMTH->get_variable_int((char *)"query_digests"); - mysql_thread___query_digests_lowercase=(bool)GloMTH->get_variable_int((char *)"query_digests_lowercase"); - mysql_thread___query_digests_replace_null=(bool)GloMTH->get_variable_int((char *)"query_digests_replace_null"); - mysql_thread___query_digests_no_digits=(bool)GloMTH->get_variable_int((char *)"query_digests_no_digits"); - mysql_thread___query_digests_normalize_digest_text=(bool)GloMTH->get_variable_int((char *)"query_digests_normalize_digest_text"); - mysql_thread___query_digests_track_hostname=(bool)GloMTH->get_variable_int((char *)"query_digests_track_hostname"); - mysql_thread___query_digests_grouping_limit=(int)GloMTH->get_variable_int((char *)"query_digests_grouping_limit"); - mysql_thread___query_digests_groups_grouping_limit=(int)GloMTH->get_variable_int((char *)"query_digests_groups_grouping_limit"); - mysql_thread___query_digests_keep_comment=(bool)GloMTH->get_variable_int((char *)"query_digests_keep_comment"); - mysql_thread___parse_failure_logs_digest=(bool)GloMTH->get_variable_int((char *)"parse_failure_logs_digest"); + REFRESH_VARIABLE_INT(handle_unknown_charset); + REFRESH_VARIABLE_INT(poll_timeout); + REFRESH_VARIABLE_INT(poll_timeout_on_failure); + REFRESH_VARIABLE_BOOL(have_compress); + REFRESH_VARIABLE_BOOL(have_ssl); + REFRESH_VARIABLE_BOOL(multiplexing); + REFRESH_VARIABLE_BOOL(log_unhealthy_connections); + REFRESH_VARIABLE_BOOL(connection_warming); + REFRESH_VARIABLE_BOOL(enforce_autocommit_on_reads); + REFRESH_VARIABLE_BOOL(autocommit_false_not_reusable); + REFRESH_VARIABLE_BOOL(autocommit_false_is_transaction); + REFRESH_VARIABLE_BOOL(verbose_query_error); + REFRESH_VARIABLE_BOOL(commands_stats); + REFRESH_VARIABLE_BOOL(query_digests); + REFRESH_VARIABLE_BOOL(query_digests_lowercase); + REFRESH_VARIABLE_BOOL(query_digests_replace_null); + REFRESH_VARIABLE_BOOL(query_digests_no_digits); + REFRESH_VARIABLE_BOOL(query_digests_normalize_digest_text); + REFRESH_VARIABLE_BOOL(query_digests_track_hostname); + REFRESH_VARIABLE_INT(query_digests_grouping_limit); + REFRESH_VARIABLE_INT(query_digests_groups_grouping_limit); + REFRESH_VARIABLE_BOOL(query_digests_keep_comment); + REFRESH_VARIABLE_BOOL(parse_failure_logs_digest); variables.min_num_servers_lantency_awareness=GloMTH->get_variable_int((char *)"min_num_servers_lantency_awareness"); variables.aurora_max_lag_ms_only_read_from_replicas=GloMTH->get_variable_int((char *)"aurora_max_lag_ms_only_read_from_replicas"); variables.stats_time_backend_query=(bool)GloMTH->get_variable_int((char *)"stats_time_backend_query"); variables.stats_time_query_processor=(bool)GloMTH->get_variable_int((char *)"stats_time_query_processor"); variables.query_cache_stores_empty_result=(bool)GloMTH->get_variable_int((char *)"query_cache_stores_empty_result"); - mysql_thread___hostgroup_manager_verbose = GloMTH->get_variable_int((char *)"hostgroup_manager_verbose"); - mysql_thread___kill_backend_connection_when_disconnect=(bool)GloMTH->get_variable_int((char *)"kill_backend_connection_when_disconnect"); - mysql_thread___client_session_track_gtid=(bool)GloMTH->get_variable_int((char *)"client_session_track_gtid"); - mysql_thread___sessions_sort=(bool)GloMTH->get_variable_int((char *)"sessions_sort"); + REFRESH_VARIABLE_INT(hostgroup_manager_verbose); + REFRESH_VARIABLE_BOOL(kill_backend_connection_when_disconnect); + REFRESH_VARIABLE_BOOL(client_session_track_gtid); + REFRESH_VARIABLE_BOOL(sessions_sort); #ifdef IDLE_THREADS - mysql_thread___session_idle_show_processlist=(bool)GloMTH->get_variable_int((char *)"session_idle_show_processlist"); + REFRESH_VARIABLE_BOOL(session_idle_show_processlist); #endif // IDLE_THREADS - mysql_thread___show_processlist_extended=GloMTH->get_variable_int((char *)"show_processlist_extended"); - mysql_thread___servers_stats=(bool)GloMTH->get_variable_int((char *)"servers_stats"); - mysql_thread___default_reconnect=(bool)GloMTH->get_variable_int((char *)"default_reconnect"); - mysql_thread___enable_client_deprecate_eof=(bool)GloMTH->get_variable_int((char *)"enable_client_deprecate_eof"); - mysql_thread___enable_server_deprecate_eof=(bool)GloMTH->get_variable_int((char *)"enable_server_deprecate_eof"); - mysql_thread___enable_load_data_local_infile=(bool)GloMTH->get_variable_int((char *)"enable_load_data_local_infile"); - mysql_thread___log_mysql_warnings_enabled=(bool)GloMTH->get_variable_int((char *)"log_mysql_warnings_enabled"); - mysql_thread___client_host_cache_size=GloMTH->get_variable_int((char *)"client_host_cache_size"); - mysql_thread___client_host_error_counts=GloMTH->get_variable_int((char *)"client_host_error_counts"); - mysql_thread___handle_warnings=GloMTH->get_variable_int((char*)"handle_warnings"); - mysql_thread___evaluate_replication_lag_on_servers_load=GloMTH->get_variable_int((char*)"evaluate_replication_lag_on_servers_load"); + REFRESH_VARIABLE_INT(show_processlist_extended); + REFRESH_VARIABLE_BOOL(servers_stats); + REFRESH_VARIABLE_BOOL(default_reconnect); + REFRESH_VARIABLE_BOOL(enable_client_deprecate_eof); + REFRESH_VARIABLE_BOOL(enable_server_deprecate_eof); + REFRESH_VARIABLE_BOOL(enable_load_data_local_infile); + REFRESH_VARIABLE_BOOL(log_mysql_warnings_enabled); + REFRESH_VARIABLE_INT(client_host_cache_size); + REFRESH_VARIABLE_INT(client_host_error_counts); + REFRESH_VARIABLE_INT(handle_warnings); + REFRESH_VARIABLE_INT(evaluate_replication_lag_on_servers_load); #ifdef DEBUG - mysql_thread___session_debug=(bool)GloMTH->get_variable_int((char *)"session_debug"); + REFRESH_VARIABLE_BOOL(session_debug); #endif /* DEBUG */ GloMTH->wrunlock(); pthread_mutex_unlock(&GloVars.global.ext_glomth_mutex); @@ -5706,7 +5778,7 @@ void MySQL_Thread::push_MyConn_local(MySQL_Connection *c) { mysrvc=(MySrvC *)c->parent; // reset insert_id #1093 c->mysql->insert_id = 0; - if (mysrvc->status==MYSQL_SERVER_STATUS_ONLINE) { + if (mysrvc->get_status() == MYSQL_SERVER_STATUS_ONLINE) { if (c->async_state_machine==ASYNC_IDLE) { cached_connections->add(c); return; // all went well diff --git a/lib/MySQL_encode.cpp b/lib/MySQL_encode.cpp new file mode 100644 index 0000000000..13a4f0461b --- /dev/null +++ b/lib/MySQL_encode.cpp @@ -0,0 +1,239 @@ +#include +#include "proxysql.h" +#include "cpp.h" + +#ifdef DEBUG +void __dump_pkt(const char *func, unsigned char *_ptr, unsigned int len) { + + if (GloVars.global.gdbg==0) return; + if (GloVars.global.gdbg_lvl[PROXY_DEBUG_MYSQL_PROTOCOL].verbosity < 8 ) return; + unsigned int i; + fprintf(stderr,"DUMP %d bytes FROM %s\n", len, func); + for(i = 0; i < len; i++) { + if(isprint(_ptr[i])) fprintf(stderr,"%c", _ptr[i]); else fprintf(stderr,"."); + if (i>0 && (i%16==15 || i==len-1)) { + unsigned int j; + if (i%16!=15) { + j=15-i%16; + while (j--) fprintf(stderr," "); + } + fprintf(stderr," --- "); + for (j=(i==len-1 ? ((int)(i/16))*16 : i-15 ) ; j<=i; j++) { + fprintf(stderr,"%02x ", _ptr[j]); + } + fprintf(stderr,"\n"); + } + } + fprintf(stderr,"\n\n"); + + +} +#endif + +char *sha1_pass_hex(char *sha1_pass) { + if (sha1_pass==NULL) return NULL; + char *buff=(char *)malloc(SHA_DIGEST_LENGTH*2+2); + buff[0]='*'; + buff[SHA_DIGEST_LENGTH*2+1]='\0'; + int i; + uint8_t a = 0; + for (i=0;iseed1= (rand_st->seed1*3+rand_st->seed2) % rand_st->max_value; + rand_st->seed2= (rand_st->seed1+rand_st->seed2+33) % rand_st->max_value; + return (((double) rand_st->seed1) / rand_st->max_value_dbl); +} + +void proxy_create_random_string(char *_to, uint length, struct rand_struct *rand_st) { + unsigned char * to = (unsigned char *)_to; + int rc = 0; + uint i; + rc = RAND_bytes((unsigned char *)to,length); +#ifdef DEBUG + if (rc==1) { + // For code coverage (to test the following code and other function) + // in DEBUG mode we pretend that RAND_bytes() fails 1% of the time + if(rand()%100==0) { + rc=0; + } + } +#endif // DEBUG + if (rc!=1) { + for (i=0; i 127) { + *to -= 128; + } + if (*to == 0) { + *to = 'a'; + } + to++; + } + } + *to= '\0'; +} + +int write_encoded_length(unsigned char *p, uint64_t val, uint8_t len, char prefix) { + if (len==1) { + *p=(char)val; + return 1; + } + *p=prefix; + p++; + memcpy(p,&val,len-1); + return len; +} + +int write_encoded_length_and_string(unsigned char *p, uint64_t val, uint8_t len, char prefix, char *string) { + int l=write_encoded_length(p,val,len,prefix); + if (val) { + memcpy(p+l,string,val); + } + return l+val; +} + +void proxy_compute_sha1_hash_multi(uint8_t *digest, const char *buf1, int len1, const char *buf2, int len2) { + PROXY_TRACE(); + const EVP_MD *evp_digest = EVP_get_digestbyname("sha1"); + assert(evp_digest != NULL); + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_MD_CTX_init(ctx); + EVP_DigestInit_ex(ctx, evp_digest, NULL); + EVP_DigestUpdate(ctx, buf1, len1); + EVP_DigestUpdate(ctx, buf2, len2); + unsigned int olen = 0; + EVP_DigestFinal(ctx, digest, &olen); + EVP_MD_CTX_free(ctx); +} + +void proxy_compute_sha1_hash(uint8_t *digest, const char *buf, int len) { + PROXY_TRACE(); + const EVP_MD *evp_digest = EVP_get_digestbyname("sha1"); + assert(evp_digest != NULL); + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_MD_CTX_init(ctx); + EVP_DigestInit_ex(ctx, evp_digest, NULL); + EVP_DigestUpdate(ctx, buf, len); + unsigned int olen = 0; + EVP_DigestFinal(ctx, digest, &olen); + EVP_MD_CTX_free(ctx); +} + +void proxy_compute_two_stage_sha1_hash(const char *password, size_t pass_len, uint8_t *hash_stage1, uint8_t *hash_stage2) { + proxy_compute_sha1_hash(hash_stage1, password, pass_len); + proxy_compute_sha1_hash(hash_stage2, (const char *) hash_stage1, SHA_DIGEST_LENGTH); +} + +void proxy_my_crypt(char *to, const uint8_t *s1, const uint8_t *s2, uint len) { + const uint8_t *s1_end= s1 + len; + while (s1 < s1_end) + *to++= *s1++ ^ *s2++; +} + +unsigned char decode_char(char x) { + if (x >= '0' && x <= '9') + return (x - 0x30); + else if (x >= 'A' && x <= 'F') + return(x - 0x37); + else if (x >= 'a' && x <= 'f') + return(x - 0x57); + else { + proxy_error("Invalid char"); + return 0; + } +} + +void unhex_pass(uint8_t *out, const char *in) { + int i=0; + for (i=0;istatus.server_connections_aborted,1); + if (err_num >= 1048 && err_num <= 1052) + return; + if (err_num >= 1054 && err_num <= 1075) + return; + if (err_num >= 1099 && err_num <= 1104) + return; + if (err_num >= 1106 && err_num <= 1113) + return; + if (err_num >= 1116 && err_num <= 1118) + return; + if (err_num == 1136 || (err_num >= 1138 && err_num <= 1149)) + return; + switch (err_num) { + case 1007: // Can't create database + case 1008: // Can't drop database + case 1044: // access denied + case 1045: // access denied +/* + case 1048: // Column cannot be null + case 1049: // Unknown database + case 1050: // Table already exists + case 1051: // Unknown table + case 1052: // Column is ambiguous +*/ + case 1120: + case 1203: // User %s already has more than 'max_user_connections' active connections + case 1226: // User '%s' has exceeded the '%s' resource (current value: %ld) + case 3118: // Access denied for user '%s'. Account is locked.. + return; + break; + default: + break; + } + time_t t=time(NULL); + if (t > time_last_detected_error) { + time_last_detected_error=t; + connect_ERR_at_time_last_detected_error=1; + } else { + if (t < time_last_detected_error) { + // time_last_detected_error is in the future + // this means that monitor has a ping interval too big and tuned that in the future + return; + } + // same time + /** + * @brief The expected configured retries set by 'mysql-connect_retries_on_failure' + '2' extra expected + * connection errors. + * @details This two extra connections errors are expected: + * 1. An initial connection error generated by the datastream and the connection when being created, + * this is, right after the session has requested a connection to the connection pool. This error takes + * places directly in the state machine from 'MySQL_Connection'. Because of this, we consider this + * additional error to be a consequence of the two states machines, and it's not considered for + * 'connect_retries'. + * 2. A second connection connection error, which is the initial connection error generated by 'MySQL_Session' + * when already in the 'CONNECTING_SERVER' state. This error is an 'extra error' to always consider, since + * it's not part of the retries specified by 'mysql_thread___connect_retries_on_failure', thus, we set the + * 'connect_retries' to be 'mysql_thread___connect_retries_on_failure + 1'. + */ + int connect_retries = mysql_thread___connect_retries_on_failure + 1; + int max_failures = mysql_thread___shun_on_failures > connect_retries ? connect_retries : mysql_thread___shun_on_failures; + + if (__sync_add_and_fetch(&connect_ERR_at_time_last_detected_error,1) >= (unsigned int)max_failures) { + bool _shu=false; + if (get_mutex==true) + MyHGM->wrlock(); // to prevent race conditions, lock here. See #627 + if (status==MYSQL_SERVER_STATUS_ONLINE) { + status=MYSQL_SERVER_STATUS_SHUNNED; + shunned_automatic=true; + _shu=true; + } else { + _shu=false; + } + if (get_mutex==true) + MyHGM->wrunlock(); + if (_shu) { + proxy_error("Shunning server %s:%d with %u errors/sec. Shunning for %u seconds\n", address, port, connect_ERR_at_time_last_detected_error , mysql_thread___shun_recovery_time_sec); + } + } + } +} + +void MySrvC::shun_and_killall() { + status=MYSQL_SERVER_STATUS_SHUNNED; + shunned_automatic=true; + shunned_and_kill_all_connections=true; +} + +MySrvC::~MySrvC() { + if (address) free(address); + if (comment) free(comment); + delete ConnectionsUsed; + delete ConnectionsFree; +} + +void MySrvC::set_status(MySerStatus _status) { + status = _status; + if (myhgc)myhgc->refresh_online_server_count(); +} diff --git a/lib/MySrvConnList.cpp b/lib/MySrvConnList.cpp new file mode 100644 index 0000000000..e2175c830b --- /dev/null +++ b/lib/MySrvConnList.cpp @@ -0,0 +1,256 @@ +#include "MySQL_HostGroups_Manager.h" + +#include "MySQL_Data_Stream.h" + +extern ProxySQL_Admin *GloAdmin; + +extern MySQL_Threads_Handler *GloMTH; + +extern MySQL_Monitor *GloMyMon; + +class MySrvConnList; +class MySrvC; +class MySrvList; +class MyHGC; + +MySQL_Connection *MySrvConnList::index(unsigned int _k) { + return (MySQL_Connection *)conns->index(_k); +} + +MySQL_Connection * MySrvConnList::remove(int _k) { + return (MySQL_Connection *)conns->remove_index_fast(_k); +} + +MySrvConnList::MySrvConnList(MySrvC *_mysrvc) { + mysrvc=_mysrvc; + conns=new PtrArray(); +} + +void MySrvConnList::add(MySQL_Connection *c) { + conns->add(c); +} + +MySrvConnList::~MySrvConnList() { + mysrvc=NULL; + while (conns_length()) { + MySQL_Connection *conn=(MySQL_Connection *)conns->remove_index_fast(0); + delete conn; + } + delete conns; +} + +void MySrvConnList::drop_all_connections() { + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Dropping all connections (%u total) on MySrvConnList %p for server %s:%d , hostgroup=%d , status=%d\n", conns_length(), this, mysrvc->address, mysrvc->port, mysrvc->myhgc->hid, (int)mysrvc->get_status()); + while (conns_length()) { + MySQL_Connection *conn=(MySQL_Connection *)conns->remove_index_fast(0); + delete conn; + } +} + +void MySrvConnList::get_random_MyConn_inner_search(unsigned int start, unsigned int end, unsigned int& conn_found_idx, unsigned int& connection_quality_level, unsigned int& number_of_matching_session_variables, const MySQL_Connection * client_conn) { + char *schema = client_conn->userinfo->schemaname; + MySQL_Connection * conn=NULL; + unsigned int k; + for (k = start; k < end; k++) { + conn = (MySQL_Connection *)conns->index(k); + if (conn->match_tracked_options(client_conn)) { + if (connection_quality_level == 0) { + // this is our best candidate so far + connection_quality_level = 1; + conn_found_idx = k; + } + if (conn->requires_CHANGE_USER(client_conn)==false) { + if (connection_quality_level == 1) { + // this is our best candidate so far + connection_quality_level = 2; + conn_found_idx = k; + } + unsigned int cnt_match = 0; // number of matching session variables + unsigned int not_match = 0; // number of not matching session variables + cnt_match = conn->number_of_matching_session_variables(client_conn, not_match); + if (strcmp(conn->userinfo->schemaname,schema)==0) { + cnt_match++; + } else { + not_match++; + } + if (not_match==0) { + // it seems we found the perfect connection + number_of_matching_session_variables = cnt_match; + connection_quality_level = 3; + conn_found_idx = k; + return; // exit immediately, we found the perfect connection + } else { + // we didn't find the perfect connection + // but maybe is better than what we have so far? + if (cnt_match > number_of_matching_session_variables) { + // this is our best candidate so far + number_of_matching_session_variables = cnt_match; + conn_found_idx = k; + } + } + } else { + if (connection_quality_level == 1) { + int rca = mysql_thread___reset_connection_algorithm; + if (rca==1) { + int ql = GloMTH->variables.connpoll_reset_queue_length; + if (ql==0) { + // if: + // mysql-reset_connection_algorithm=1 and + // mysql-connpoll_reset_queue_length=0 + // we will not return a connection with connection_quality_level == 1 + // because we want to run COM_CHANGE_USER + // This change was introduced to work around Galera bug + // https://github.com/codership/galera/issues/613 + connection_quality_level = 0; + } + } + } + } + } + } +} + + + +MySQL_Connection * MySrvConnList::get_random_MyConn(MySQL_Session *sess, bool ff) { + MySQL_Connection * conn=NULL; + unsigned int i; + unsigned int conn_found_idx; + unsigned int l=conns_length(); + unsigned int connection_quality_level = 0; + bool needs_warming = false; + // connection_quality_level: + // 0 : not found any good connection, tracked options are not OK + // 1 : tracked options are OK , but CHANGE USER is required + // 2 : tracked options are OK , CHANGE USER is not required, but some SET statement or INIT_DB needs to be executed + // 3 : tracked options are OK , CHANGE USER is not required, and it seems that SET statements or INIT_DB ARE not required + unsigned int number_of_matching_session_variables = 0; // this includes session variables AND schema + bool connection_warming = mysql_thread___connection_warming; + int free_connections_pct = mysql_thread___free_connections_pct; + if (mysrvc->myhgc->attributes.configured == true) { + // mysql_hostgroup_attributes takes priority + connection_warming = mysrvc->myhgc->attributes.connection_warming; + free_connections_pct = mysrvc->myhgc->attributes.free_connections_pct; + } + if (connection_warming == true) { + unsigned int total_connections = mysrvc->ConnectionsFree->conns_length()+mysrvc->ConnectionsUsed->conns_length(); + unsigned int expected_warm_connections = free_connections_pct*mysrvc->max_connections/100; + if (total_connections < expected_warm_connections) { + needs_warming = true; + } + } + if (l && ff==false && needs_warming==false) { + if (l>32768) { + i=rand()%l; + } else { + i=fastrand()%l; + } + if (sess && sess->client_myds && sess->client_myds->myconn && sess->client_myds->myconn->userinfo) { + MySQL_Connection * client_conn = sess->client_myds->myconn; + get_random_MyConn_inner_search(i, l, conn_found_idx, connection_quality_level, number_of_matching_session_variables, client_conn); + if (connection_quality_level !=3 ) { // we didn't find the perfect connection + get_random_MyConn_inner_search(0, i, conn_found_idx, connection_quality_level, number_of_matching_session_variables, client_conn); + } + // connection_quality_level: + // 1 : tracked options are OK , but CHANGE USER is required + // 2 : tracked options are OK , CHANGE USER is not required, but some SET statement or INIT_DB needs to be executed + switch (connection_quality_level) { + case 0: // not found any good connection, tracked options are not OK + // we must check if connections need to be freed before + // creating a new connection + { + unsigned int conns_free = mysrvc->ConnectionsFree->conns_length(); + unsigned int conns_used = mysrvc->ConnectionsUsed->conns_length(); + unsigned int pct_max_connections = (3 * mysrvc->max_connections) / 4; + unsigned int connections_to_free = 0; + + if (conns_free >= 1) { + // connection cleanup is triggered when connections exceed 3/4 of the total + // allowed max connections, this cleanup ensures that at least *one connection* + // will be freed. + if (pct_max_connections <= (conns_free + conns_used)) { + connections_to_free = (conns_free + conns_used) - pct_max_connections; + if (connections_to_free == 0) connections_to_free = 1; + } + + while (conns_free && connections_to_free) { + MySQL_Connection* conn = mysrvc->ConnectionsFree->remove(0); + delete conn; + + conns_free = mysrvc->ConnectionsFree->conns_length(); + connections_to_free -= 1; + } + } + + // we must create a new connection + conn = new MySQL_Connection(); + conn->parent=mysrvc; + // if attributes.multiplex == true , STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG is set to false. And vice-versa + conn->set_status(!conn->parent->myhgc->attributes.multiplex, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); + __sync_fetch_and_add(&MyHGM->status.server_connections_created, 1); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); + } + break; + case 1: //tracked options are OK , but CHANGE USER is required + // we may consider creating a new connection + { + unsigned int conns_free = mysrvc->ConnectionsFree->conns_length(); + unsigned int conns_used = mysrvc->ConnectionsUsed->conns_length(); + if ((conns_used > conns_free) && (mysrvc->max_connections > (conns_free/2 + conns_used/2)) ) { + conn = new MySQL_Connection(); + conn->parent=mysrvc; + // if attributes.multiplex == true , STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG is set to false. And vice-versa + conn->set_status(!conn->parent->myhgc->attributes.multiplex, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); + __sync_fetch_and_add(&MyHGM->status.server_connections_created, 1); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); + } else { + conn=(MySQL_Connection *)conns->remove_index_fast(conn_found_idx); + } + } + break; + case 2: // tracked options are OK , CHANGE USER is not required, but some SET statement or INIT_DB needs to be executed + case 3: // tracked options are OK , CHANGE USER is not required, and it seems that SET statements or INIT_DB ARE not required + // here we return the best connection we have, no matter if connection_quality_level is 2 or 3 + conn=(MySQL_Connection *)conns->remove_index_fast(conn_found_idx); + break; + default: // this should never happen + // LCOV_EXCL_START + assert(0); + break; + // LCOV_EXCL_STOP + } + } else { + conn=(MySQL_Connection *)conns->remove_index_fast(i); + } + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); + return conn; + } else { + unsigned long long curtime = monotonic_time(); + curtime = curtime / 1000 / 1000; // convert to second + MyHGC *_myhgc = mysrvc->myhgc; + if (curtime > _myhgc->current_time_now) { + _myhgc->current_time_now = curtime; + _myhgc->new_connections_now = 0; + } + _myhgc->new_connections_now++; + unsigned int throttle_connections_per_sec_to_hostgroup = (unsigned int) mysql_thread___throttle_connections_per_sec_to_hostgroup; + if (_myhgc->attributes.configured == true) { + // mysql_hostgroup_attributes takes priority + throttle_connections_per_sec_to_hostgroup = _myhgc->attributes.throttle_connections_per_sec; + } + if (_myhgc->new_connections_now > (unsigned int) throttle_connections_per_sec_to_hostgroup) { + __sync_fetch_and_add(&MyHGM->status.server_connections_delayed, 1); + return NULL; + } else { + conn = new MySQL_Connection(); + conn->parent=mysrvc; + // if attributes.multiplex == true , STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG is set to false. And vice-versa + conn->set_status(!conn->parent->myhgc->attributes.multiplex, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); + __sync_fetch_and_add(&MyHGM->status.server_connections_created, 1); + proxy_debug(PROXY_DEBUG_MYSQL_CONNPOOL, 7, "Returning MySQL Connection %p, server %s:%d\n", conn, conn->parent->address, conn->parent->port); + return conn; + } + } + return NULL; // never reach here +} + diff --git a/lib/MySrvList.cpp b/lib/MySrvList.cpp new file mode 100644 index 0000000000..ab39ef22e7 --- /dev/null +++ b/lib/MySrvList.cpp @@ -0,0 +1,46 @@ +#include "MySQL_HostGroups_Manager.h" + +class MySrvConnList; +class MySrvC; +class MySrvList; +class MyHGC; + +MySrvList::MySrvList(MyHGC *_myhgc) { + myhgc=_myhgc; + servers=new PtrArray(); +} + +void MySrvList::add(MySrvC *s) { + if (s->myhgc==NULL) { + s->myhgc=myhgc; + } + servers->add(s); + myhgc->refresh_online_server_count(); +} + + +int MySrvList::find_idx(MySrvC *s) { + for (unsigned int i=0; ilen; i++) { + MySrvC *mysrv=(MySrvC *)servers->index(i); + if (mysrv==s) { + return (unsigned int)i; + } + } + return -1; +} + +void MySrvList::remove(MySrvC *s) { + int i=find_idx(s); + assert(i>=0); + servers->remove_index_fast((unsigned int)i); + myhgc->refresh_online_server_count(); +} + +MySrvList::~MySrvList() { + myhgc=NULL; + while (servers->len) { + MySrvC *mysrvc=(MySrvC *)servers->remove_index_fast(0); + delete mysrvc; + } + delete servers; +} diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 9177d59d29..fb22b6aeab 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -6043,6 +6043,7 @@ ProxySQL_Admin::ProxySQL_Admin() : variables.p_memory_metrics_interval = 61; #ifdef DEBUG variables.debug=GloVars.global.gdbg; + all_modules_started = false; debug_output = 1; proxysql_set_admin_debug_output(debug_output); #endif /* DEBUG */ @@ -6125,6 +6126,12 @@ void ProxySQL_Admin::init_ldap() { } } +void ProxySQL_Admin::init_http_server() { + AdminHTTPServer = new ProxySQL_HTTP_Server(); + AdminHTTPServer->init(); + AdminHTTPServer->print_version(); +} + struct boot_srv_info_t { string member_id; string member_host; @@ -6395,11 +6402,9 @@ bool ProxySQL_Admin::init(const bootstrap_info_t& bootstrap_info) { } Admin_HTTP_Server = NULL; - AdminHTTPServer = new ProxySQL_HTTP_Server(); - AdminHTTPServer->init(); - AdminHTTPServer->print_version(); - AdminRestApiServer = NULL; + AdminHTTPServer = NULL; + /* AdminRestApiServer = new ProxySQL_RESTAPI_Server(); AdminRestApiServer->print_version(); @@ -7234,245 +7239,284 @@ void ProxySQL_Admin::load_or_update_global_settings(SQLite3DB *db) { } } -void ProxySQL_Admin::flush_admin_variables___database_to_runtime( - SQLite3DB *db, bool replace, const string& checksum, const time_t epoch, bool lock -) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ADMIN variables. Replace:%d\n", replace); - char *error=NULL; - int cols=0; - int affected_rows=0; - SQLite3_result *resultset=NULL; - char *q=(char *)"SELECT substr(variable_name,7) vn, variable_value FROM global_variables WHERE variable_name LIKE 'admin-%'"; - admindb->execute_statement(q, &error , &cols , &affected_rows , &resultset); - if (error) { - proxy_error("Error on %s : %s\n", q, error); - return; - } else { - wrlock(); - for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { - SQLite3_row *r=*it; - bool rc=set_variable(r->fields[0],r->fields[1], lock); - if (rc==false) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - if (replace) { - char *val=get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(r->fields[0],(char *)"version")) { - proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); - } - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"admin-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } else { - if (strcmp(r->fields[0],(char *)"debug")==0) { - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"admin-%s\"",r->fields[0]); - db->execute(q); - } else { - proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); - } - sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"admin-%s\"",r->fields[0]); - db->execute(q); - } - free(val); - } - } else { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - } +void ProxySQL_Admin::load_restapi_server() { + if (!all_modules_started) { return; } + + std::function(const httpserver::http_request&)> prometheus_callback { + [this](const httpserver::http_request& request) { + auto headers = request_headers(request); + auto serial_response = this->serial_exposer(headers); + auto http_response = make_response(serial_response); + + return http_response; } - //commit(); NOT IMPLEMENTED + }; - // Checksums are always generated - 'admin-checksum_*' deprecated - { - pthread_mutex_lock(&GloVars.checksum_mutex); - // generate checksum for cluster - flush_admin_variables___runtime_to_database(admindb, false, false, false, true); - char *error=NULL; - int cols=0; - int affected_rows=0; - SQLite3_result *resultset=NULL; - std::string q; - if (GloVars.cluster_sync_interfaces) { - q="SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE 'admin-\%' ORDER BY variable_name"; - } else { - q="SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE 'admin-\%' AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_ADMIN) + " ORDER BY variable_name"; - } - admindb->execute_statement(q.c_str(), &error , &cols , &affected_rows , &resultset); - uint64_t hash1 = resultset->raw_checksum(); - uint32_t d32[2]; - char buf[20]; - memcpy(&d32, &hash1, sizeof(hash1)); - sprintf(buf,"0x%0X%0X", d32[0], d32[1]); - GloVars.checksums_values.admin_variables.set_checksum(buf); - GloVars.checksums_values.admin_variables.version++; - time_t t = time(NULL); - if (epoch != 0 && checksum != "" && GloVars.checksums_values.admin_variables.checksum == checksum) { - GloVars.checksums_values.admin_variables.epoch = epoch; + bool free_restapi_port = false; + + // Helper lambda taking a boolean reference as a parameter to check if 'restapi_port' is available. + // In case of port not being free or error, logs an error 'ProxySQL_RestAPI_Server' isn't able to be started. + const auto check_restapi_port = [&](bool& restapi_port_free) -> void { + int e_port_check = check_port_availability(variables.restapi_port, &restapi_port_free); + + if (restapi_port_free == false) { + if (e_port_check == -1) { + proxy_error("Unable to start 'ProxySQL_RestAPI_Server', failed to set 'SO_REUSEADDR' to check port availability.\n"); } else { - GloVars.checksums_values.admin_variables.epoch = t; + proxy_error( + "Unable to start 'ProxySQL_RestAPI_Server', port '%d' already in use.\n", + variables.restapi_port + ); } - GloVars.epoch_version = t; - GloVars.generate_global_checksum(); - GloVars.checksums_values.updates_cnt++; - pthread_mutex_unlock(&GloVars.checksum_mutex); - proxy_info( - "Computed checksum for 'LOAD ADMIN VARIABLES TO RUNTIME' was '%s', with epoch '%llu'\n", - GloVars.checksums_values.admin_variables.checksum, GloVars.checksums_values.admin_variables.epoch + } + }; + + if (variables.restapi_enabled != variables.restapi_enabled_old) { + if (variables.restapi_enabled) { + check_restapi_port(free_restapi_port); + } + + if (variables.restapi_enabled && free_restapi_port) { + AdminRestApiServer = new ProxySQL_RESTAPI_Server( + variables.restapi_port, {{"/metrics", prometheus_callback}} ); - delete resultset; + } else { + delete AdminRestApiServer; + AdminRestApiServer = NULL; } - wrunlock(); - { - std::function(const httpserver::http_request&)> prometheus_callback { - [this](const httpserver::http_request& request) { - auto headers = request_headers(request); - auto serial_response = this->serial_exposer(headers); - auto http_response = make_response(serial_response); + variables.restapi_enabled_old = variables.restapi_enabled; + } else { + if (variables.restapi_port != variables.restapi_port_old) { + if (AdminRestApiServer) { + delete AdminRestApiServer; + AdminRestApiServer = NULL; + } - return http_response; - } - }; + if (variables.restapi_enabled) { + check_restapi_port(free_restapi_port); + } - bool free_restapi_port = false; + if (variables.restapi_enabled && free_restapi_port) { + AdminRestApiServer = new ProxySQL_RESTAPI_Server( + variables.restapi_port, {{"/metrics", prometheus_callback}} + ); + } + variables.restapi_port_old = variables.restapi_port; + } + } +} - // Helper lambda taking a boolean reference as a parameter to check if 'restapi_port' is available. - // In case of port not being free or error, logs an error 'ProxySQL_RestAPI_Server' isn't able to be started. - const auto check_restapi_port = [&](bool& restapi_port_free) -> void { - int e_port_check = check_port_availability(variables.restapi_port, &restapi_port_free); +void ProxySQL_Admin::load_http_server() { + if (!all_modules_started) { return; } - if (restapi_port_free == false) { - if (e_port_check == -1) { - proxy_error("Unable to start 'ProxySQL_RestAPI_Server', failed to set 'SO_REUSEADDR' to check port availability.\n"); - } else { + if (variables.web_enabled != variables.web_enabled_old) { + if (variables.web_enabled) { + if (GloVars.web_interface_plugin == NULL) { + char *key_pem; + char *cert_pem; + GloVars.get_SSL_pem_mem(&key_pem, &cert_pem); + Admin_HTTP_Server = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG | MHD_USE_SSL, + variables.web_port, + NULL, NULL, http_handler, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, + MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 4, + MHD_OPTION_NONCE_NC_SIZE, (unsigned int) 300, + MHD_OPTION_HTTPS_MEM_KEY, key_pem, + MHD_OPTION_HTTPS_MEM_CERT, cert_pem, + MHD_OPTION_END); + free(key_pem); + free(cert_pem); + } else { + if (GloWebInterface) { + int sfd = 0; + int reuseaddr = 1; + struct sockaddr_in tmp_addr; + + sfd = socket(AF_INET, SOCK_STREAM, 0); + memset(&tmp_addr, 0, sizeof(tmp_addr)); + tmp_addr.sin_family = AF_INET; + tmp_addr.sin_port = htons(variables.web_port); + tmp_addr.sin_addr.s_addr = INADDR_ANY; + + if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseaddr, sizeof(reuseaddr)) == -1) { + close(sfd); proxy_error( - "Unable to start 'ProxySQL_RestAPI_Server', port '%d' already in use.\n", - variables.restapi_port + "Unable to start WebInterfacePlugin, failed to set 'SO_REUSEADDR' to check port '%d' availability.\n", + variables.web_port ); + } else { + if (::bind(sfd, (struct sockaddr*)&tmp_addr, (socklen_t)sizeof(tmp_addr)) == -1) { + close(sfd); + proxy_error( + "Unable to start WebInterfacePlugin, port '%d' already in use.\n", + variables.web_port + ); + } else { + close(sfd); + GloWebInterface->start(variables.web_port); + } } } - }; - - if (variables.restapi_enabled != variables.restapi_enabled_old) { - if (variables.restapi_enabled) { - check_restapi_port(free_restapi_port); + } + } else { + if (GloVars.web_interface_plugin == NULL) { + MHD_stop_daemon(Admin_HTTP_Server); + Admin_HTTP_Server = NULL; + } else { + if (GloWebInterface) { + GloWebInterface->stop(); } - - if (variables.restapi_enabled && free_restapi_port) { - AdminRestApiServer = new ProxySQL_RESTAPI_Server( - variables.restapi_port, {{"/metrics", prometheus_callback}} - ); + } + } + variables.web_enabled_old = variables.web_enabled; + } else { + if (variables.web_port != variables.web_port_old) { + if (variables.web_enabled) { + if (GloVars.web_interface_plugin == NULL) { + MHD_stop_daemon(Admin_HTTP_Server); + Admin_HTTP_Server = NULL; + char *key_pem; + char *cert_pem; + GloVars.get_SSL_pem_mem(&key_pem, &cert_pem); + Admin_HTTP_Server = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG | MHD_USE_SSL, + variables.web_port, + NULL, NULL, http_handler, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, + MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 4, + MHD_OPTION_NONCE_NC_SIZE, (unsigned int) 300, + MHD_OPTION_HTTPS_MEM_KEY, key_pem, + MHD_OPTION_HTTPS_MEM_CERT, cert_pem, + MHD_OPTION_END); + free(key_pem); + free(cert_pem); } else { - delete AdminRestApiServer; - AdminRestApiServer = NULL; - } - variables.restapi_enabled_old = variables.restapi_enabled; - } else { - if (variables.restapi_port != variables.restapi_port_old) { - if (AdminRestApiServer) { - delete AdminRestApiServer; - AdminRestApiServer = NULL; + if (GloWebInterface) { + GloWebInterface->start(variables.web_port); } + } + } + variables.web_port_old = variables.web_port; + } + } +} - if (variables.restapi_enabled) { - check_restapi_port(free_restapi_port); - } - if (variables.restapi_enabled && free_restapi_port) { - AdminRestApiServer = new ProxySQL_RESTAPI_Server( - variables.restapi_port, {{"/metrics", prometheus_callback}} - ); - } - variables.restapi_port_old = variables.restapi_port; +bool ProxySQL_Admin::flush_GENERIC_variables__retrieve__database_to_runtime(const std::string& modname, char* &error, int& cols, int& affected_rows, SQLite3_result* &resultset) { + string q = "SELECT substr(variable_name," + to_string(modname.length()+2) + ") vn, variable_value FROM global_variables WHERE variable_name LIKE '" + modname + "-%'"; + admindb->execute_statement(q.c_str(), &error , &cols , &affected_rows , &resultset); + if (error) { + proxy_error("Error on %s : %s\n", q.c_str(), error); + free(error); + return false; + } + return true; +} + +void ProxySQL_Admin::flush_GENERIC_variables__process__database_to_runtime( + const string& modname, SQLite3DB *db, SQLite3_result* resultset, + const bool& lock, const bool& replace, + const std::unordered_set& variables_read_only, + const std::unordered_set& variables_to_delete_silently, + const std::unordered_set& variables_deprecated, + const std::unordered_set& variables_special_values, + std::function special_variable_action +) { + for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { + SQLite3_row *r=*it; + bool rc = false; + if (modname == "admin") { + rc = set_variable(r->fields[0],r->fields[1], lock); + } else if (modname == "mysql") { + rc = GloMTH->set_variable(r->fields[0],r->fields[1]); + } else if (modname == "sqliteserver") { + rc = GloSQLite3Server->set_variable(r->fields[0],r->fields[1]); +#ifdef PROXYSQLCLICKHOUSE + } else if (modname == "clickhouse") { + rc = GloClickHouseServer->set_variable(r->fields[0],r->fields[1]); +#endif // PROXYSQLCLICKHOUSE + } else if (modname == "ldap") { + rc = GloMyLdapAuth->set_variable(r->fields[0],r->fields[1]); + } + const string v = string(r->fields[0]); + if (rc==false) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); + if (replace) { + char *val = NULL; + if (modname == "admin") { + val = get_variable(r->fields[0]); + } else if (modname == "mysql") { + val = GloMTH->get_variable(r->fields[0]); + } else if (modname == "sqliteserver") { + val = GloSQLite3Server->get_variable(r->fields[0]); +#ifdef PROXYSQLCLICKHOUSE + } else if (modname == "clickhouse") { + val = GloClickHouseServer->get_variable(r->fields[0]); +#endif // PROXYSQLCLICKHOUSE + } else if (modname == "ldap") { + val = GloMyLdapAuth->get_variable(r->fields[0]); } - } - if (variables.web_enabled != variables.web_enabled_old) { - if (variables.web_enabled) { - if (GloVars.web_interface_plugin == NULL) { - char *key_pem; - char *cert_pem; - GloVars.get_SSL_pem_mem(&key_pem, &cert_pem); - Admin_HTTP_Server = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG | MHD_USE_SSL, - variables.web_port, - NULL, NULL, http_handler, NULL, - MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, - MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 4, - MHD_OPTION_NONCE_NC_SIZE, (unsigned int) 300, - MHD_OPTION_HTTPS_MEM_KEY, key_pem, - MHD_OPTION_HTTPS_MEM_CERT, cert_pem, - MHD_OPTION_END); - free(key_pem); - free(cert_pem); + char q[1000]; + if (val) { + if (variables_read_only.count(v) > 0) { + proxy_warning("Impossible to set read-only variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); } else { - if (GloWebInterface) { - int sfd = 0; - int reuseaddr = 1; - struct sockaddr_in tmp_addr; - - sfd = socket(AF_INET, SOCK_STREAM, 0); - memset(&tmp_addr, 0, sizeof(tmp_addr)); - tmp_addr.sin_family = AF_INET; - tmp_addr.sin_port = htons(variables.web_port); - tmp_addr.sin_addr.s_addr = INADDR_ANY; - - if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseaddr, sizeof(reuseaddr)) == -1) { - close(sfd); - proxy_error( - "Unable to start WebInterfacePlugin, failed to set 'SO_REUSEADDR' to check port '%d' availability.\n", - variables.web_port - ); - } else { - if (::bind(sfd, (struct sockaddr*)&tmp_addr, (socklen_t)sizeof(tmp_addr)) == -1) { - close(sfd); - proxy_error( - "Unable to start WebInterfacePlugin, port '%d' already in use.\n", - variables.web_port - ); - } else { - close(sfd); - GloWebInterface->start(variables.web_port); - } - } - } + proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); } + sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"%s-%s\",\"%s\")", modname.c_str(), r->fields[0],val); + db->execute(q); + free(val); } else { - if (GloVars.web_interface_plugin == NULL) { - MHD_stop_daemon(Admin_HTTP_Server); - Admin_HTTP_Server = NULL; + if (variables_to_delete_silently.count(v) > 0) { + sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"%s-%s\"", modname.c_str(), r->fields[0]); + db->execute(q); + } else if (variables_deprecated.count(v) > 0) { + proxy_error("Global variable %s-%s is deprecated.\n", modname.c_str(), r->fields[0]); + sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"%s-%s\"", modname.c_str(), r->fields[0]); + db->execute(q); } else { - if (GloWebInterface) { - GloWebInterface->stop(); - } + proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); } + sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"%s-%s\"", modname.c_str(), r->fields[0]); + db->execute(q); } - variables.web_enabled_old = variables.web_enabled; - } else { - if (variables.web_port != variables.web_port_old) { - if (variables.web_enabled) { - if (GloVars.web_interface_plugin == NULL) { - MHD_stop_daemon(Admin_HTTP_Server); - Admin_HTTP_Server = NULL; - char *key_pem; - char *cert_pem; - GloVars.get_SSL_pem_mem(&key_pem, &cert_pem); - Admin_HTTP_Server = MHD_start_daemon(MHD_USE_AUTO | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG | MHD_USE_SSL, - variables.web_port, - NULL, NULL, http_handler, NULL, - MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, MHD_OPTION_STRICT_FOR_CLIENT, (int) 1, - MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 4, - MHD_OPTION_NONCE_NC_SIZE, (unsigned int) 300, - MHD_OPTION_HTTPS_MEM_KEY, key_pem, - MHD_OPTION_HTTPS_MEM_CERT, cert_pem, - MHD_OPTION_END); - free(key_pem); - free(cert_pem); - } else { - if (GloWebInterface) { - GloWebInterface->start(variables.web_port); - } - } - } - variables.web_port_old = variables.web_port; + } + } else { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); + if (variables_special_values.count(v) > 0) { + if (special_variable_action != nullptr) { + special_variable_action(v, r->fields[1], db); } } + } + } +} + +void ProxySQL_Admin::flush_admin_variables___database_to_runtime( + SQLite3DB *db, bool replace, const string& checksum, const time_t epoch, bool lock +) { + proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing ADMIN variables. Replace:%d\n", replace); + char *error=NULL; + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + if (flush_GENERIC_variables__retrieve__database_to_runtime("admin", error, cols, affected_rows, resultset) == true) { + wrlock(); + flush_GENERIC_variables__process__database_to_runtime("admin", db, resultset, lock, replace, {"version"}, {"debug"}, {}, {}); + //commit(); NOT IMPLEMENTED + + // Checksums are always generated - 'admin-checksum_*' deprecated + + { + // generate checksum for cluster + pthread_mutex_lock(&GloVars.checksum_mutex); + flush_admin_variables___runtime_to_database(admindb, false, false, false, true); + flush_GENERIC_variables__checksum__database_to_runtime("admin", checksum, epoch); + pthread_mutex_unlock(&GloVars.checksum_mutex); + } + wrunlock(); + { + load_http_server(); + load_restapi_server(); // Update the admin variable for 'web_verbosity' admin___web_verbosity = variables.web_verbosity; } @@ -7480,82 +7524,88 @@ void ProxySQL_Admin::flush_admin_variables___database_to_runtime( if (resultset) delete resultset; } +void ProxySQL_Admin::flush_GENERIC_variables__checksum__database_to_runtime(const string& modname, const string& checksum, const time_t epoch) { + char *error=NULL; + int cols=0; + int affected_rows=0; + SQLite3_result *resultset=NULL; + std::string q; + q="SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE '" + modname + "-\%' "; + if (modname == "mysql") { + q += " AND variable_name NOT IN ('mysql-threads')"; + if (GloVars.cluster_sync_interfaces == false) { + q += " AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_MYSQL); + } + } else if (modname == "admin") { + if (GloVars.cluster_sync_interfaces == false) { + q += " AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_ADMIN); + } + } + q += " ORDER BY variable_name"; + admindb->execute_statement(q.c_str(), &error , &cols , &affected_rows , &resultset); + uint64_t hash1 = resultset->raw_checksum(); + uint32_t d32[2]; + char buf[20]; + memcpy(&d32, &hash1, sizeof(hash1)); + sprintf(buf,"0x%0X%0X", d32[0], d32[1]); + ProxySQL_Checksum_Value *checkvar = NULL; + if (modname == "admin") { + checkvar = &GloVars.checksums_values.admin_variables; + } else if (modname == "mysql") { + checkvar = &GloVars.checksums_values.mysql_variables; + } + assert(checkvar != NULL); + checkvar->set_checksum(buf); + checkvar->version++; + time_t t = time(NULL); + if (epoch != 0 && checksum != "" && checkvar->checksum == checksum) { + checkvar->epoch = epoch; + } else { + checkvar->epoch = t; + } + GloVars.epoch_version = t; + GloVars.generate_global_checksum(); + GloVars.checksums_values.updates_cnt++; + string modnameupper = modname; + for (char &c : modnameupper) { c = std::toupper(c); } + proxy_info( + "Computed checksum for 'LOAD %s VARIABLES TO RUNTIME' was '%s', with epoch '%llu'\n", + modnameupper.c_str(), checkvar->checksum, checkvar->epoch + ); + delete resultset; +} + void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db, bool replace, const std::string& checksum, const time_t epoch) { proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MySQL variables. Replace:%d\n", replace); char *error=NULL; int cols=0; int affected_rows=0; SQLite3_result *resultset=NULL; - char *q=(char *)"SELECT substr(variable_name,7) vn, variable_value FROM global_variables WHERE variable_name LIKE 'mysql-%'"; - admindb->execute_statement(q, &error , &cols , &affected_rows , &resultset); - if (error) { - proxy_error("Error on %s : %s\n", q, error); - return; - } else { + if (flush_GENERIC_variables__retrieve__database_to_runtime("mysql", error, cols, affected_rows, resultset) == true) { GloMTH->wrlock(); char * previous_default_charset = GloMTH->get_variable_string((char *)"default_charset"); char * previous_default_collation_connection = GloMTH->get_variable_string((char *)"default_collation_connection"); assert(previous_default_charset); assert(previous_default_collation_connection); - for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { - SQLite3_row *r=*it; - const char *value = r->fields[1]; - bool rc=GloMTH->set_variable(r->fields[0],value); - if (rc==false) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],value); - if (replace) { - char *val=GloMTH->get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(val,value)) { - proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],value, val); - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } - free(val); - } else { - if (strcmp(r->fields[0],(char *)"session_debug")==0) { - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); - db->execute(q); - } else { - if (strcmp(r->fields[0],(char *)"forward_autocommit")==0) { - if (strcasecmp(value,"true")==0 || strcasecmp(value,"1")==0) { - proxy_error("Global variable mysql-forward_autocommit is deprecated. See issue #3253\n"); - } - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); - db->execute(q); - } else { - proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); - } - } - sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"mysql-%s\"",r->fields[0]); + flush_GENERIC_variables__process__database_to_runtime("mysql", db, resultset, false, replace, {}, {"session_debug"}, {"forward_autocommit"}, + {"default_collation_connection", "default_charset", "show_processlist_extended"}, + [](const std::string& varname, const char *varvalue, SQLite3DB* db) { + if (varname == "default_collation_connection" || varname == "default_charset") { + char *val=GloMTH->get_variable((char *)varname.c_str()); + if (val) { + if (strcmp(val,varvalue)) { + char q[1000]; + proxy_warning("Variable %s with value \"%s\" is being replaced with value \"%s\".\n", varname.c_str(), varvalue, val); + sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")", varname.c_str() ,val); db->execute(q); } + free(val); } - } else { - if ( - (strcmp(r->fields[0],"default_collation_connection")==0) - || (strcmp(r->fields[0],"default_charset")==0) - ) { - char *val=GloMTH->get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(val,value)) { - proxy_warning("Variable %s with value \"%s\" is being replaced with value \"%s\".\n", r->fields[0],value, val); - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"mysql-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } - free(val); - } - } - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],value); - if (strcmp(r->fields[0],(char *)"show_processlist_extended")==0) { - variables.mysql_show_processlist_extended = atoi(value); - } + } else if (varname == "show_processlist_extended") { + GloAdmin->variables.mysql_show_processlist_extended = atoi(varvalue); } -// } - } - + } + ); char q[1000]; char * default_charset = GloMTH->get_variable_string((char *)"default_charset"); char * default_collation_connection = GloMTH->get_variable_string((char *)"default_collation_connection"); @@ -7630,47 +7680,15 @@ void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db, GloMTH->commit(); GloMTH->wrunlock(); - // Checksums are always generated - 'admin-checksum_*' deprecated { // NOTE: 'GloMTH->wrunlock()' should have been called before this point to avoid possible // deadlocks. See issue #3847. pthread_mutex_lock(&GloVars.checksum_mutex); // generate checksum for cluster flush_mysql_variables___runtime_to_database(admindb, false, false, false, true, true); - char *error=NULL; - int cols=0; - int affected_rows=0; - SQLite3_result *resultset=NULL; - std::string q; - q = "SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE 'mysql-\%' AND variable_name NOT IN ('mysql-threads')"; - if (GloVars.cluster_sync_interfaces == false) { - q += " AND variable_name NOT IN " + string(CLUSTER_SYNC_INTERFACES_MYSQL); - } - q += " ORDER BY variable_name"; - admindb->execute_statement(q.c_str(), &error , &cols , &affected_rows , &resultset); - uint64_t hash1 = resultset->raw_checksum(); - uint32_t d32[2]; - char buf[20]; - memcpy(&d32, &hash1, sizeof(hash1)); - sprintf(buf,"0x%0X%0X", d32[0], d32[1]); - GloVars.checksums_values.mysql_variables.set_checksum(buf); - GloVars.checksums_values.mysql_variables.version++; - time_t t = time(NULL); - if (epoch != 0 && checksum != "" && GloVars.checksums_values.mysql_variables.checksum == checksum) { - GloVars.checksums_values.mysql_variables.epoch = epoch; - } else { - GloVars.checksums_values.mysql_variables.epoch = t; - } - GloVars.epoch_version = t; - GloVars.generate_global_checksum(); - GloVars.checksums_values.updates_cnt++; + flush_GENERIC_variables__checksum__database_to_runtime("mysql", checksum, epoch); pthread_mutex_unlock(&GloVars.checksum_mutex); - delete resultset; } - proxy_info( - "Computed checksum for 'LOAD MYSQL VARIABLES TO RUNTIME' was '%s', with epoch '%llu'\n", - GloVars.checksums_values.mysql_variables.checksum, GloVars.checksums_values.mysql_variables.epoch - ); } if (resultset) delete resultset; } @@ -7688,43 +7706,9 @@ void ProxySQL_Admin::flush_sqliteserver_variables___database_to_runtime(SQLite3D int cols=0; int affected_rows=0; SQLite3_result *resultset=NULL; - char *q=(char *)"SELECT substr(variable_name,14) vn, variable_value FROM global_variables WHERE variable_name LIKE 'sqliteserver-%'"; - admindb->execute_statement(q, &error , &cols , &affected_rows , &resultset); - if (error) { - proxy_error("Error on %s : %s\n", q, error); - return; - } else { + if (flush_GENERIC_variables__retrieve__database_to_runtime("sqliteserver", error, cols, affected_rows, resultset) == true) { GloSQLite3Server->wrlock(); - for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { - SQLite3_row *r=*it; - bool rc=GloSQLite3Server->set_variable(r->fields[0],r->fields[1]); - if (rc==false) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - if (replace) { - char *val=GloSQLite3Server->get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(val,r->fields[1])) { - proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"sqliteserver-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } - free(val); - } else { - if (strcmp(r->fields[0],(char *)"session_debug")==0) { - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"sqliteserver-%s\"",r->fields[0]); - db->execute(q); - } else { - proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); - } - sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"sqliteserver-%s\"",r->fields[0]); - db->execute(q); - } - } - } else { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - } - } + flush_GENERIC_variables__process__database_to_runtime("sqliteserver", db, resultset, false, replace, {}, {"session_debug"}, {}, {}); //GloClickHouse->commit(); GloSQLite3Server->wrunlock(); } @@ -7813,43 +7797,9 @@ void ProxySQL_Admin::flush_clickhouse_variables___database_to_runtime(SQLite3DB int cols=0; int affected_rows=0; SQLite3_result *resultset=NULL; - char *q=(char *)"SELECT substr(variable_name,12) vn, variable_value FROM global_variables WHERE variable_name LIKE 'clickhouse-%'"; - admindb->execute_statement(q, &error , &cols , &affected_rows , &resultset); - if (error) { - proxy_error("Error on %s : %s\n", q, error); - return; - } else { + if (flush_GENERIC_variables__retrieve__database_to_runtime("clickhouse", error, cols, affected_rows, resultset) == true) { GloClickHouseServer->wrlock(); - for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { - SQLite3_row *r=*it; - bool rc=GloClickHouseServer->set_variable(r->fields[0],r->fields[1]); - if (rc==false) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - if (replace) { - char *val=GloClickHouseServer->get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(val,r->fields[1])) { - proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"clickhouse-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } - free(val); - } else { - if (strcmp(r->fields[0],(char *)"session_debug")==0) { - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"clickhouse-%s\"",r->fields[0]); - db->execute(q); - } else { - proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); - } - sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"clickhouse-%s\"",r->fields[0]); - db->execute(q); - } - } - } else { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - } - } + flush_GENERIC_variables__process__database_to_runtime("clickhouse", db, resultset, false, replace, {}, {"session_debug"}, {}, {}); //GloClickHouse->commit(); GloClickHouseServer->wrunlock(); } @@ -8338,44 +8288,9 @@ void ProxySQL_Admin::flush_ldap_variables___database_to_runtime(SQLite3DB *db, b int cols=0; int affected_rows=0; SQLite3_result *resultset=NULL; - char *q=(char *)"SELECT substr(variable_name,6) vn, variable_value FROM global_variables WHERE variable_name LIKE 'ldap-%'"; - admindb->execute_statement(q, &error , &cols , &affected_rows , &resultset); - if (error) { - proxy_error("Error on %s : %s\n", q, error); - free(error); //fix a memory leak when call admindb->execute_statement function - return; - } else { + if (flush_GENERIC_variables__retrieve__database_to_runtime("ldap", error, cols, affected_rows, resultset) == true) { GloMyLdapAuth->wrlock(); - for (std::vector::iterator it = resultset->rows.begin() ; it != resultset->rows.end(); ++it) { - SQLite3_row *r=*it; - bool rc=GloMyLdapAuth->set_variable(r->fields[0],r->fields[1]); - if (rc==false) { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Impossible to set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - if (replace) { - char *val=GloMyLdapAuth->get_variable(r->fields[0]); - char q[1000]; - if (val) { - if (strcmp(val,r->fields[1])) { - proxy_warning("Impossible to set variable %s with value \"%s\". Resetting to current \"%s\".\n", r->fields[0],r->fields[1], val); - sprintf(q,"INSERT OR REPLACE INTO global_variables VALUES(\"ldap-%s\",\"%s\")",r->fields[0],val); - db->execute(q); - } - free(val); - } else { - if (strcmp(r->fields[0],(char *)"session_debug")==0) { - sprintf(q,"DELETE FROM disk.global_variables WHERE variable_name=\"ldap-%s\"",r->fields[0]); - db->execute(q); - } else { - proxy_warning("Impossible to set not existing variable %s with value \"%s\". Deleting. If the variable name is correct, this version doesn't support it\n", r->fields[0],r->fields[1]); - } - sprintf(q,"DELETE FROM global_variables WHERE variable_name=\"ldap-%s\"",r->fields[0]); - db->execute(q); - } - } - } else { - proxy_debug(PROXY_DEBUG_ADMIN, 4, "Set variable %s with value \"%s\"\n", r->fields[0],r->fields[1]); - } - } + flush_GENERIC_variables__process__database_to_runtime("admin", db, resultset, false, replace, {}, {}, {}, {}); GloMyLdapAuth->wrunlock(); // Checksums are always generated - 'admin-checksum_*' deprecated @@ -8383,35 +8298,9 @@ void ProxySQL_Admin::flush_ldap_variables___database_to_runtime(SQLite3DB *db, b pthread_mutex_lock(&GloVars.checksum_mutex); // generate checksum for cluster flush_ldap_variables___runtime_to_database(admindb, false, false, false, true); - char *error=NULL; - int cols=0; - int affected_rows=0; - SQLite3_result *resultset=NULL; - char *q=(char *)"SELECT variable_name, variable_value FROM runtime_global_variables WHERE variable_name LIKE 'ldap-\%' ORDER BY variable_name"; - admindb->execute_statement(q, &error , &cols , &affected_rows , &resultset); - uint64_t hash1 = resultset->raw_checksum(); - uint32_t d32[2]; - char buf[20]; - memcpy(&d32, &hash1, sizeof(hash1)); - sprintf(buf,"0x%0X%0X", d32[0], d32[1]); - GloVars.checksums_values.ldap_variables.set_checksum(buf); - GloVars.checksums_values.ldap_variables.version++; - time_t t = time(NULL); - if (epoch != 0 && checksum != "" && GloVars.checksums_values.ldap_variables.checksum == checksum) { - GloVars.checksums_values.ldap_variables.epoch = epoch; - } else { - GloVars.checksums_values.ldap_variables.epoch = t; - } - GloVars.epoch_version = t; - GloVars.generate_global_checksum(); - GloVars.checksums_values.updates_cnt++; + flush_GENERIC_variables__checksum__database_to_runtime("ldap", checksum, epoch); pthread_mutex_unlock(&GloVars.checksum_mutex); - delete resultset; } - proxy_info( - "Computed checksum for 'LOAD LDAP VARIABLES TO RUNTIME' was '%s', with epoch '%llu'\n", - GloVars.checksums_values.ldap_variables.checksum, GloVars.checksums_values.ldap_variables.epoch - ); } if (resultset) delete resultset; } diff --git a/lib/ProxySQL_Cluster.cpp b/lib/ProxySQL_Cluster.cpp index 514182b491..64a870c9e3 100644 --- a/lib/ProxySQL_Cluster.cpp +++ b/lib/ProxySQL_Cluster.cpp @@ -63,28 +63,20 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { char *query1 = (char *)"SELECT GLOBAL_CHECKSUM()"; // in future this will be used for "light check" char *query2 = (char *)"SELECT * FROM stats_mysql_global ORDER BY Variable_Name"; char *query3 = (char *)"SELECT * FROM runtime_checksums_values ORDER BY name"; - char *username = NULL; - char *password = NULL; bool rc_bool = true; int query_error_counter = 0; char *query_error = NULL; int cluster_check_status_frequency_count = 0; MYSQL *conn = mysql_init(NULL); -// goto __exit_monitor_thread; + if (conn==NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_monitor_thread; } while (glovars.shutdown == 0 && rc_bool == true) { - MYSQL * rc_conn = NULL; - int rc_query = 0; - bool update_checksum = false; - if (username) { free(username); } - if (password) { free(password); } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty - unsigned int timeout = 1; - // unsigned int timeout_long = 60; + cluster_creds_t creds(GloProxyCluster->get_credentials()); + + if (creds.user.size()) { // do not monitor if the username is empty if (conn == NULL) { conn = mysql_init(NULL); if (conn==NULL) { @@ -92,23 +84,22 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { goto __exit_monitor_thread; } } + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. + unsigned int timeout = 1; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); - //mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout_long); - //mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout); { unsigned char val = 1; mysql_options(conn, MYSQL_OPT_SSL_ENFORCE, &val); mysql_options(conn, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); } - //rc_conn = mysql_real_connect(conn, node->hostname, username, password, NULL, node->port, NULL, CLIENT_COMPRESS); // FIXME: add optional support for compression + // FIXME: add optional support for compression proxy_debug(PROXY_DEBUG_CLUSTER, 5, "Connecting to peer %s:%d\n", node->hostname, node->port); - rc_conn = mysql_real_connect(conn, node->get_host_address(), username, password, NULL, node->port, NULL, 0); -// if (rc_conn) { -// } - //char *query = query1; + MYSQL* rc_conn = mysql_real_connect(conn, node->get_host_address(), creds.user.c_str(), creds.pass.c_str(), NULL, node->port, NULL, 0); + if (rc_conn) { MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); - rc_query = mysql_query(conn,(char *)"SELECT @@version"); + int rc_query = mysql_query(conn,(char *)"SELECT @@version"); if (rc_query == 0) { query_error = NULL; query_error_counter = 0; @@ -151,7 +142,7 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { } while ( glovars.shutdown == 0 && rc_query == 0 && rc_bool == true) { unsigned long long start_time=monotonic_time(); - //unsigned long long before_query_time=monotonic_time(); + rc_query = mysql_query(conn,query1); if ( rc_query == 0 ) { query_error = NULL; @@ -159,23 +150,11 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { MYSQL_RES *result = mysql_store_result(conn); //unsigned long long after_query_time=monotonic_time(); //unsigned long long elapsed_time_us = (after_query_time - before_query_time); - update_checksum = GloProxyCluster->Update_Global_Checksum(node->hostname, node->port, result); + bool update_checksum = GloProxyCluster->Update_Global_Checksum(node->hostname, node->port, result); mysql_free_result(result); // FIXME: update metrics are not updated for now. We only check checksum //rc_bool = GloProxyCluster->Update_Node_Metrics(node->hostname, node->port, result, elapsed_time_us); - //unsigned long long elapsed_time_ms = elapsed_time_us / 1000; -/* - int e_ms = (int)elapsed_time_ms; - //fprintf(stderr,"Elapsed time = %d ms\n", e_ms); - int ci = __sync_fetch_and_add(&GloProxyCluster->cluster_check_interval_ms,0); - if (ci > e_ms) { - if (rc_bool) { - usleep((ci-e_ms)*1000); // remember, usleep is in us - } - } -*/ - //query = query3; - //unsigned long long before_query_time2=monotonic_time(); + if (update_checksum) { unsigned long long before_query_time=monotonic_time(); rc_query = mysql_query(conn,query3); @@ -183,37 +162,22 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { query_error = NULL; query_error_counter = 0; MYSQL_RES *result = mysql_store_result(conn); - //unsigned long long after_query_time2=monotonic_time(); - //unsigned long long elapsed_time_us2 = (after_query_time2 - before_query_time2); rc_bool = GloProxyCluster->Update_Node_Checksums(node->hostname, node->port, result); mysql_free_result(result); - //unsigned long long elapsed_time_ms2 = elapsed_time_us2 / 1000; - //int e_ms = (int)elapsed_time_ms + int(elapsed_time_ms2); - //fprintf(stderr,"Elapsed time = %d ms\n", e_ms); - //int ci = __sync_fetch_and_add(&GloProxyCluster->cluster_check_interval_ms,0); - //if (ci > e_ms) { - // if (rc_bool) { - // tts = 1; - // //usleep((ci-e_ms)*1000); // remember, usleep is in us - // } - //} } else { query_error = query3; if (query_error_counter == 0) { unsigned long long after_query_time=monotonic_time(); unsigned long long elapsed_time_us = (after_query_time - before_query_time); - proxy_error("Cluster: unable to run query on %s:%d using user %s after %llums : %s . Error: %s\n", node->hostname, node->port , username, elapsed_time_us/1000 , query_error, mysql_error(conn)); + proxy_error( + "Cluster: unable to run query on %s:%d using user %s after %llums : %s . Error: %s\n", + node->hostname, node->port, creds.user.c_str(), elapsed_time_us/1000, query_error, mysql_error(conn) + ); } if (++query_error_counter == QUERY_ERROR_RATE) query_error_counter = 0; } } else { GloProxyCluster->Update_Node_Checksums(node->hostname, node->port); - //int ci = __sync_fetch_and_add(&GloProxyCluster->cluster_check_interval_ms,0); - //if (ci > elapsed_time_ms) { - // if (rc_bool) { - // usleep((ci-elapsed_time_ms)*1000); // remember, usleep is in us - // } - //} } if (rc_query == 0) { cluster_check_status_frequency_count++; @@ -235,7 +199,10 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { if (query_error_counter == 0) { unsigned long long after_query_time=monotonic_time(); unsigned long long elapsed_time_us = (after_query_time - before_query_time); - proxy_error("Cluster: unable to run query on %s:%d using user %s after %llums : %s . Error: %s\n", node->hostname, node->port , username, elapsed_time_us/1000 , query_error, mysql_error(conn)); + proxy_error( + "Cluster: unable to run query on %s:%d using user %s after %llums : %s . Error: %s\n", + node->hostname, node->port, creds.user.c_str(), elapsed_time_us/1000, query_error, mysql_error(conn) + ); } if (++query_error_counter == QUERY_ERROR_RATE) query_error_counter = 0; } @@ -246,7 +213,10 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { if (query_error_counter == 0) { unsigned long long after_query_time=monotonic_time(); unsigned long long elapsed_time_us = (after_query_time - start_time); - proxy_error("Cluster: unable to run query on %s:%d using user %s after %llums : %s . Error: %s\n", node->hostname, node->port , username, elapsed_time_us/1000, query_error, mysql_error(conn)); + proxy_error( + "Cluster: unable to run query on %s:%d using user %s after %llums : %s . Error: %s\n", + node->hostname, node->port, creds.user.c_str(), elapsed_time_us/1000, query_error, mysql_error(conn) + ); } if (++query_error_counter == QUERY_ERROR_RATE) query_error_counter = 0; } @@ -290,9 +260,7 @@ void * ProxySQL_Cluster_Monitor_thread(void *args) { } proxy_info("Cluster: closing thread for peer %s:%d\n", node->hostname, node->port); delete node; - //pthread_exit(0); mysql_thread_end(); - //GloProxyCluster->thread_ending(node->thrid); __sync_fetch_and_sub(&GloVars.statuses.stack_memory_cluster_threads,tmp_stack_size); @@ -1176,40 +1144,38 @@ void ProxySQL_Cluster::pull_mysql_query_rules_from_peer(const string& expected_c pthread_mutex_lock(&GloProxyCluster->update_mysql_query_rules_mutex); nodes.get_peer_to_sync_mysql_query_rules(&hostname, &port, &ip_address); if (hostname) { - char *username = NULL; - char *password = NULL; - // bool rc_bool = true; - MYSQL *rc_conn; - int rc_query; - int rc; + cluster_creds_t creds {}; + MYSQL *conn = mysql_init(NULL); if (conn==NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_pull_mysql_query_rules_from_peer; } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty + + creds = GloProxyCluster->get_credentials(); + if (creds.user.size()) { // do not monitor if the username is empty + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. unsigned int timeout = 1; - // unsigned int timeout_long = 60; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); - //mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout_long); - //mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout); { unsigned char val = 1; mysql_options(conn, MYSQL_OPT_SSL_ENFORCE, &val); mysql_options(conn, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); } proxy_debug(PROXY_DEBUG_CLUSTER, 5, "Fetching MySQL Query Rules from peer %s:%d started. Expected checksum: %s\n", hostname, port, expected_checksum.c_str()); proxy_info("Cluster: Fetching MySQL Query Rules from peer %s:%d started. Expected checksum: %s\n", hostname, port, expected_checksum.c_str()); - rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, username, password, NULL, port, NULL, 0); + MYSQL* rc_conn = mysql_real_connect( + conn, ip_address ? ip_address : hostname, creds.user.c_str(), creds.pass.c_str(), NULL, port, NULL, 0 + ); if (rc_conn) { MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); MYSQL_RES *result1 = NULL; MYSQL_RES *result2 = NULL; //rc_query = mysql_query(conn,"SELECT rule_id, username, schemaname, flagIN, client_addr, proxy_addr, proxy_port, digest, match_digest, match_pattern, negate_match_pattern, re_modifiers, flagOUT, replace_pattern, destination_hostgroup, cache_ttl, cache_empty_result, cache_timeout, reconnect, timeout, retries, delay, next_query_flagIN, mirror_flagOUT, mirror_hostgroup, error_msg, ok_msg, sticky_conn, multiplex, gtid_from_hostgroup, log, apply, attributes, comment FROM runtime_mysql_query_rules"); - rc_query = mysql_query(conn,CLUSTER_QUERY_MYSQL_QUERY_RULES); + int rc_query = mysql_query(conn,CLUSTER_QUERY_MYSQL_QUERY_RULES); if ( rc_query == 0 ) { - MYSQL_RES *result1 = mysql_store_result(conn); + result1 = mysql_store_result(conn); rc_query = mysql_query(conn,CLUSTER_QUERY_MYSQL_QUERY_RULES_FAST_ROUTING); if ( rc_query == 0) { result2 = mysql_store_result(conn); @@ -1237,7 +1203,7 @@ void ProxySQL_Cluster::pull_mysql_query_rules_from_peer(const string& expected_c sqlite3_stmt *statement1 = NULL; //sqlite3 *mydb3 = GloAdmin->admindb->get_db(); //rc=(*proxy_sqlite3_prepare_v2)(mydb3, q, -1, &statement1, 0); - rc = GloAdmin->admindb->prepare_v2(q, &statement1); + int rc = GloAdmin->admindb->prepare_v2(q, &statement1); ASSERT_SQLITE_OK(rc, GloAdmin->admindb); GloAdmin->admindb->execute("BEGIN TRANSACTION"); while ((row = mysql_fetch_row(result1))) { @@ -1280,6 +1246,7 @@ void ProxySQL_Cluster::pull_mysql_query_rules_from_peer(const string& expected_c rc=(*proxy_sqlite3_clear_bindings)(statement1); ASSERT_SQLITE_OK(rc, GloAdmin->admindb); rc=(*proxy_sqlite3_reset)(statement1); ASSERT_SQLITE_OK(rc, GloAdmin->admindb); } + (*proxy_sqlite3_finalize)(statement1); GloAdmin->admindb->execute("COMMIT"); @@ -1323,6 +1290,8 @@ void ProxySQL_Cluster::pull_mysql_query_rules_from_peer(const string& expected_c } row_idx++; } + (*proxy_sqlite3_finalize)(statement1fr); + (*proxy_sqlite3_finalize)(statement32fr); //GloAdmin->admindb->execute("PRAGMA integrity_check"); GloAdmin->admindb->execute("COMMIT"); @@ -1470,22 +1439,20 @@ void ProxySQL_Cluster::pull_mysql_users_from_peer(const string& expected_checksu pthread_mutex_lock(&GloProxyCluster->update_mysql_users_mutex); nodes.get_peer_to_sync_mysql_users(&hostname, &port, &ip_address); if (hostname) { - char *username = NULL; - char *password = NULL; - MYSQL *rc_conn; - int rc_query; + cluster_creds_t creds {}; + MYSQL *conn = mysql_init(NULL); if (conn==NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_pull_mysql_users_from_peer; } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty + + creds = GloProxyCluster->get_credentials(); + if (creds.user.size()) { // do not monitor if the username is empty + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. unsigned int timeout = 1; - // unsigned int timeout_long = 60; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); - //mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout_long); - //mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout); { unsigned char val = 1; mysql_options(conn, MYSQL_OPT_SSL_ENFORCE, &val); mysql_options(conn, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); @@ -1493,7 +1460,7 @@ void ProxySQL_Cluster::pull_mysql_users_from_peer(const string& expected_checksu proxy_debug(PROXY_DEBUG_CLUSTER, 5, "Fetching MySQL Users from peer %s:%d started. Expected checksum: %s\n", hostname, port, expected_checksum.c_str()); proxy_info("Cluster: Fetching MySQL Users from peer %s:%d started. Expected checksum: %s\n", hostname, port, expected_checksum.c_str()); - rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, username, password, NULL, port, NULL, 0); + MYSQL* rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, creds.user.c_str(), creds.pass.c_str(), NULL, port, NULL, 0); if (rc_conn == nullptr) { proxy_debug(PROXY_DEBUG_CLUSTER, 5, "Fetching MySQL Users from peer %s:%d failed: %s\n", hostname, port, mysql_error(conn)); proxy_info("Cluster: Fetching MySQL Users from peer %s:%d failed: %s\n", hostname, port, mysql_error(conn)); @@ -1512,7 +1479,7 @@ void ProxySQL_Cluster::pull_mysql_users_from_peer(const string& expected_checksu MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); - rc_query = mysql_query(conn, CLUSTER_QUERY_MYSQL_USERS); + int rc_query = mysql_query(conn, CLUSTER_QUERY_MYSQL_USERS); if (rc_query == 0) { MYSQL_RES* mysql_users_result = mysql_store_result(conn); MYSQL_RES* ldap_mapping_result = nullptr; @@ -1621,12 +1588,6 @@ void ProxySQL_Cluster::pull_mysql_users_from_peer(const string& expected_checksu } } } - if (username) { - free(username); - } - if (password) { - free(password); - } __exit_pull_mysql_users_from_peer: if (conn) { if (conn->net.pvio) { @@ -1771,17 +1732,18 @@ void ProxySQL_Cluster::pull_runtime_mysql_servers_from_peer(const runtime_mysql_ pthread_mutex_lock(&GloProxyCluster->update_runtime_mysql_servers_mutex); nodes.get_peer_to_sync_runtime_mysql_servers(&hostname, &port, &peer_checksum, &ip_address); if (hostname) { - char *username = NULL; - char *password = NULL; - // bool rc_bool = true; - MYSQL *rc_conn; + cluster_creds_t creds {}; + MYSQL *conn = mysql_init(NULL); if (conn==NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_pull_mysql_servers_from_peer; } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty + + creds = GloProxyCluster->get_credentials(); + if (creds.user.size()) { // do not monitor if the username is empty + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. unsigned int timeout = 1; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); { @@ -1790,7 +1752,9 @@ void ProxySQL_Cluster::pull_runtime_mysql_servers_from_peer(const runtime_mysql_ } proxy_debug(PROXY_DEBUG_CLUSTER, 5, "Fetching 'MySQL Servers' from peer %s:%d started. Expected checksum %s\n", hostname, port, peer_checksum); proxy_info("Cluster: Fetching 'MySQL Servers' from peer %s:%d started. Expected checksum %s\n", hostname, port, peer_checksum); - rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, username, password, NULL, port, NULL, 0); + MYSQL* rc_conn = mysql_real_connect( + conn, ip_address ? ip_address : hostname, creds.user.c_str(), creds.pass.c_str(), NULL, port, NULL, 0 + ); if (rc_conn) { MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); @@ -1856,12 +1820,6 @@ void ProxySQL_Cluster::pull_runtime_mysql_servers_from_peer(const runtime_mysql_ fetch_failed = true; } } - if (username) { - free(username); - } - if (password) { - free(password); - } __exit_pull_mysql_servers_from_peer: if (conn) { if (conn->net.pvio) { @@ -1924,29 +1882,29 @@ void ProxySQL_Cluster::pull_mysql_servers_v2_from_peer(const mysql_servers_v2_ch nodes.get_peer_to_sync_mysql_servers_v2(&hostname, &port, &peer_mysql_servers_v2_checksum, &peer_runtime_mysql_servers_checksum, &ip_address); if (hostname) { - char* username = NULL; - char* password = NULL; - // bool rc_bool = true; - MYSQL* rc_conn; + cluster_creds_t creds {}; + MYSQL* conn = mysql_init(NULL); if (conn == NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_pull_mysql_servers_v2_from_peer; } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty + + creds = GloProxyCluster->get_credentials(); + if (creds.user.size()) { // do not monitor if the username is empty + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. unsigned int timeout = 1; - // unsigned int timeout_long = 60; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); - //mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout_long); - //mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout); { unsigned char val = 1; mysql_options(conn, MYSQL_OPT_SSL_ENFORCE, &val); mysql_options(conn, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); } proxy_debug(PROXY_DEBUG_CLUSTER, 5, "Fetching MySQL Servers v2 from peer %s:%d started. Expected checksum %s\n", hostname, port, peer_mysql_servers_v2_checksum); proxy_info("Cluster: Fetching MySQL Servers v2 from peer %s:%d started. Expected checksum %s\n", hostname, port, peer_mysql_servers_v2_checksum); - rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, username, password, NULL, port, NULL, 0); + MYSQL* rc_conn = mysql_real_connect( + conn, ip_address ? ip_address : hostname, creds.user.c_str(), creds.pass.c_str(), NULL, port, NULL, 0 + ); if (rc_conn) { MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); @@ -2395,12 +2353,6 @@ void ProxySQL_Cluster::pull_mysql_servers_v2_from_peer(const mysql_servers_v2_ch fetch_failed = true; } } - if (username) { - free(username); - } - if (password) { - free(password); - } __exit_pull_mysql_servers_v2_from_peer: if (conn) { if (conn->net.pvio) { @@ -2461,31 +2413,28 @@ void ProxySQL_Cluster::pull_global_variables_from_peer(const string& var_type, c } if (hostname) { - char *username = NULL; - char *password = NULL; - MYSQL *rc_conn = nullptr; - int rc_query = 0; - int rc = 0; - MYSQL *conn = mysql_init(NULL); + cluster_creds_t creds {}; + MYSQL *conn = mysql_init(NULL); if (conn == NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_pull_mysql_variables_from_peer; } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty + creds = GloProxyCluster->get_credentials(); + if (creds.user.size()) { // do not monitor if the username is empty + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. unsigned int timeout = 1; - // unsigned int timeout_long = 60; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); - //mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout_long); - //mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout); { unsigned char val = 1; mysql_options(conn, MYSQL_OPT_SSL_ENFORCE, &val); mysql_options(conn, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); } proxy_info("Cluster: Fetching %s variables from peer %s:%d started\n", vars_type_str, hostname, port); - rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, username, password, NULL, port, NULL, 0); + MYSQL* rc_conn = mysql_real_connect( + conn, ip_address ? ip_address : hostname, creds.user.c_str(), creds.pass.c_str(), NULL, port, NULL, 0 + ); if (rc_conn) { MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); @@ -2503,7 +2452,7 @@ void ProxySQL_Cluster::pull_global_variables_from_peer(const string& var_type, c } } s_query += " ORDER BY variable_name"; - mysql_query(conn, s_query.c_str()); + int rc_query = mysql_query(conn, s_query.c_str()); if (rc_query == 0) { MYSQL_RES *result = mysql_store_result(conn); @@ -2535,7 +2484,7 @@ void ProxySQL_Cluster::pull_global_variables_from_peer(const string& var_type, c MYSQL_ROW row; char *q = (char *)"INSERT OR REPLACE INTO global_variables (variable_name, variable_value) VALUES (?1 , ?2)"; sqlite3_stmt *statement1 = NULL; - rc = GloAdmin->admindb->prepare_v2(q, &statement1); + int rc = GloAdmin->admindb->prepare_v2(q, &statement1); ASSERT_SQLITE_OK(rc, GloAdmin->admindb); while ((row = mysql_fetch_row(result))) { @@ -2604,12 +2553,6 @@ void ProxySQL_Cluster::pull_global_variables_from_peer(const string& var_type, c fetch_failed = true; } } - if (username) { - free(username); - } - if (password) { - free(password); - } __exit_pull_mysql_variables_from_peer: if (conn) { if (conn->net.pvio) { @@ -2633,23 +2576,20 @@ void ProxySQL_Cluster::pull_proxysql_servers_from_peer(const std::string& expect pthread_mutex_lock(&GloProxyCluster->update_proxysql_servers_mutex); nodes.get_peer_to_sync_proxysql_servers(&hostname, &port, &ip_address); if (hostname) { - char *username = NULL; - char *password = NULL; - // bool rc_bool = true; - MYSQL *rc_conn; - int rc_query; + cluster_creds_t creds {}; + MYSQL *conn = mysql_init(NULL); if (conn==NULL) { proxy_error("Unable to run mysql_init()\n"); goto __exit_pull_proxysql_servers_from_peer; } - GloProxyCluster->get_credentials(&username, &password); - if (strlen(username)) { // do not monitor if the username is empty + + creds = GloProxyCluster->get_credentials(); + if (creds.user.size()) { // do not monitor if the username is empty + // READ/WRITE timeouts were enforced as an attempt to prevent deadlocks in the original + // implementation. They were proven unnecessary, leaving only 'CONNECT_TIMEOUT'. unsigned int timeout = 1; - // unsigned int timeout_long = 60; mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout); - //mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout_long); - //mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout); { unsigned char val = 1; mysql_options(conn, MYSQL_OPT_SSL_ENFORCE, &val); mysql_options(conn, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); @@ -2660,11 +2600,13 @@ void ProxySQL_Cluster::pull_proxysql_servers_from_peer(const std::string& expect "Cluster: Fetching ProxySQL Servers from peer %s:%d started. Expected checksum: %s\n", hostname, port, expected_checksum.c_str() ); - rc_conn = mysql_real_connect(conn, ip_address ? ip_address : hostname, username, password, NULL, port, NULL, 0); + MYSQL* rc_conn = mysql_real_connect( + conn, ip_address ? ip_address : hostname, creds.user.c_str(), creds.pass.c_str(), NULL, port, NULL, 0 + ); if (rc_conn) { MySQL_Monitor::update_dns_cache_from_mysql_conn(conn); - rc_query = mysql_query(conn,"SELECT hostname, port, weight, comment FROM runtime_proxysql_servers ORDER BY hostname, port"); + int rc_query = mysql_query(conn,"SELECT hostname, port, weight, comment FROM runtime_proxysql_servers ORDER BY hostname, port"); if ( rc_query == 0 ) { MYSQL_RES* result = mysql_store_result(conn); uint64_t proxy_servers_hash = mysql_raw_checksum(result); @@ -2738,12 +2680,6 @@ void ProxySQL_Cluster::pull_proxysql_servers_from_peer(const std::string& expect fetch_failed = true; } } - if (username) { - free(username); - } - if (password) { - free(password); - } __exit_pull_proxysql_servers_from_peer: if (conn) { if (conn->net.pvio) { @@ -4467,11 +4403,13 @@ void ProxySQL_Cluster::p_update_metrics() { }; // this function returns credentials to the caller, used by monitoring threads -void ProxySQL_Cluster::get_credentials(char **username, char **password) { +cluster_creds_t ProxySQL_Cluster::get_credentials() { pthread_mutex_lock(&mutex); - *username = strdup(cluster_username); - *password = strdup(cluster_password); + const string user { cluster_username }; + const string pass { cluster_password }; pthread_mutex_unlock(&mutex); + + return { user, pass }; } void ProxySQL_Cluster::set_username(char *_username) { diff --git a/lib/ProxySQL_HTTP_Server.cpp b/lib/ProxySQL_HTTP_Server.cpp index ff38885582..d8affaf396 100644 --- a/lib/ProxySQL_HTTP_Server.cpp +++ b/lib/ProxySQL_HTTP_Server.cpp @@ -586,7 +586,7 @@ int ProxySQL_HTTP_Server::handler(void *cls, struct MHD_Connection *connection, delete cpu_sqlite; s.append(""); - response = MHD_create_response_from_buffer(s.length(), (void *) s.c_str(), MHD_RESPMEM_PERSISTENT); + response = MHD_create_response_from_buffer(s.length(), (void *) s.c_str(), MHD_RESPMEM_MUST_COPY); ret = MHD_queue_response (connection, MHD_HTTP_OK, response); MHD_destroy_response (response); return ret; diff --git a/lib/QP_query_digest_stats.cpp b/lib/QP_query_digest_stats.cpp new file mode 100644 index 0000000000..509951b506 --- /dev/null +++ b/lib/QP_query_digest_stats.cpp @@ -0,0 +1,189 @@ +#include "query_processor.h" + +// reverse: reverse string s in place +static void reverse(char s[]) { + int i, j; + char c; + int l = strlen(s); + for (i = 0, j = l-1; i 0); /* delete it */ + s[i] = '\0'; + reverse(s); +} + + +QP_query_digest_stats::QP_query_digest_stats(char *u, char *s, uint64_t d, char *dt, int h, char *ca) { + digest=d; + digest_text=NULL; + if (dt) { + digest_text=strndup(dt, mysql_thread___query_digests_max_digest_length); + } + if (strlen(u) < sizeof(username_buf)) { + strcpy(username_buf,u); + username = username_buf; + } else { + username=strdup(u); + } + if (strlen(s) < sizeof(schemaname_buf)) { + strcpy(schemaname_buf,s); + schemaname = schemaname_buf; + } else { + schemaname=strdup(s); + } + if (strlen(ca) < sizeof(client_address_buf)) { + strcpy(client_address_buf,ca); + client_address = client_address_buf; + } else { + client_address=strdup(ca); + } + count_star=0; + first_seen=0; + last_seen=0; + sum_time=0; + min_time=0; + max_time=0; + rows_affected=0; + rows_sent=0; + hid=h; +} +void QP_query_digest_stats::add_time( + unsigned long long t, unsigned long long n, unsigned long long ra, unsigned long long rs, + unsigned long long cnt +) { + count_star += cnt; + sum_time+=t; + rows_affected+=ra; + rows_sent+=rs; + if (t < min_time || min_time==0) { + if (t) min_time = t; + } + if (t > max_time) { + max_time = t; + } + if (first_seen==0) { + first_seen=n; + } + last_seen=n; +} +QP_query_digest_stats::~QP_query_digest_stats() { + if (digest_text) { + free(digest_text); + digest_text=NULL; + } + if (username) { + if (username == username_buf) { + } else { + free(username); + } + username=NULL; + } + if (schemaname) { + if (schemaname == schemaname_buf) { + } else { + free(schemaname); + } + schemaname=NULL; + } + if (client_address) { + if (client_address == client_address_buf) { + } else { + free(client_address); + } + client_address=NULL; + } +} + +// Funtion to get the digest text associated to a QP_query_digest_stats. +// QP_query_digest_stats member type "char *digest_text" may by NULL, so we +// have to get the digest text from "digest_text_umap". +char *QP_query_digest_stats::get_digest_text(const umap_query_digest_text *digest_text_umap) { + char *digest_text_str = NULL; + + if (digest_text) { + digest_text_str = digest_text; + } else { + std::unordered_map::const_iterator it; + it = digest_text_umap->find(digest); + if (it != digest_text_umap->end()) { + digest_text_str = it->second; + } else { + // LCOV_EXCL_START + assert(0); + // LCOV_EXCL_STOP + } + } + + return digest_text_str; +} + +char **QP_query_digest_stats::get_row(umap_query_digest_text *digest_text_umap, query_digest_stats_pointers_t *qdsp) { + char **pta=qdsp->pta; + + assert(schemaname); + pta[0]=schemaname; + assert(username); + pta[1]=username; + assert(client_address); + pta[2]=client_address; + + assert(qdsp != NULL); + assert(qdsp->digest); + sprintf(qdsp->digest,"0x%016llX", (long long unsigned int)digest); + pta[3]=qdsp->digest; + + pta[4] = get_digest_text(digest_text_umap); + + //sprintf(qdsp->count_star,"%u",count_star); + my_itoa(qdsp->count_star, count_star); + pta[5]=qdsp->count_star; + + time_t __now; + time(&__now); + unsigned long long curtime=monotonic_time(); + time_t seen_time; + seen_time= __now - curtime/1000000 + first_seen/1000000; + //sprintf(qdsp->first_seen,"%ld", seen_time); + my_itoa(qdsp->first_seen, seen_time); + pta[6]=qdsp->first_seen; + + seen_time= __now - curtime/1000000 + last_seen/1000000; + //sprintf(qdsp->last_seen,"%ld", seen_time); + my_itoa(qdsp->last_seen, seen_time); + pta[7]=qdsp->last_seen; + //sprintf(qdsp->sum_time,"%llu",sum_time); + my_itoa(qdsp->sum_time,sum_time); + pta[8]=qdsp->sum_time; + //sprintf(qdsp->min_time,"%llu",min_time); + my_itoa(qdsp->min_time,min_time); + pta[9]=qdsp->min_time; + //sprintf(qdsp->max_time,"%llu",max_time); + my_itoa(qdsp->max_time,max_time); + pta[10]=qdsp->max_time; + // we are reverting this back to the use of sprintf instead of my_itoa + // because with my_itoa we are losing the sign + // see issue #2285 + sprintf(qdsp->hid,"%d",hid); + //my_itoa(qdsp->hid,hid); + pta[11]=qdsp->hid; + //sprintf(qdsp->rows_affected,"%llu",rows_affected); + my_itoa(qdsp->rows_affected,rows_affected); + pta[12]=qdsp->rows_affected; + //sprintf(qdsp->rows_sent,"%llu",rows_sent); + my_itoa(qdsp->rows_sent,rows_sent); + pta[13]=qdsp->rows_sent; + return pta; +} + diff --git a/lib/QP_rule_text.cpp b/lib/QP_rule_text.cpp new file mode 100644 index 0000000000..cd1a340425 --- /dev/null +++ b/lib/QP_rule_text.cpp @@ -0,0 +1,99 @@ +/* +#include // std::cout +#include // std::sort +#include // std::vector +#include "re2/re2.h" +#include "re2/regexp.h" +#include "proxysql.h" +#include "cpp.h" + +#include "MySQL_PreparedStatement.h" +#include "MySQL_Data_Stream.h" +*/ +#include "query_processor.h" +#include +#include "proxysql_macros.h" + +#include "QP_rule_text.h" + + +QP_rule_text_hitsonly::QP_rule_text_hitsonly(QP_rule_t *QPr) { + pta=NULL; + pta=(char **)malloc(sizeof(char *)*2); + itostr(pta[0], (long long)QPr->rule_id); + itostr(pta[1], (long long)QPr->hits); +} + +QP_rule_text_hitsonly::~QP_rule_text_hitsonly() { + for(int i=0; i<2; i++) { + free_null(pta[i]); + } + free(pta); +} + +QP_rule_text::QP_rule_text(QP_rule_t *QPr) { + num_fields=36; // this count the number of fields + pta=NULL; + pta=(char **)malloc(sizeof(char *)*num_fields); + itostr(pta[0], (long long)QPr->rule_id); + itostr(pta[1], (long long)QPr->active); + pta[2]=strdup_null(QPr->username); + pta[3]=strdup_null(QPr->schemaname); + itostr(pta[4], (long long)QPr->flagIN); + + pta[5]=strdup_null(QPr->client_addr); + pta[6]=strdup_null(QPr->proxy_addr); + itostr(pta[7], (long long)QPr->proxy_port); + + char buf[20]; + if (QPr->digest) { + sprintf(buf,"0x%016llX", (long long unsigned int)QPr->digest); + pta[8]=strdup(buf); + } else { + pta[8]=NULL; + } + + pta[9]=strdup_null(QPr->match_digest); + pta[10]=strdup_null(QPr->match_pattern); + itostr(pta[11], (long long)QPr->negate_match_pattern); + std::string re_mod; + re_mod=""; + if ((QPr->re_modifiers & QP_RE_MOD_CASELESS) == QP_RE_MOD_CASELESS) re_mod = "CASELESS"; + if ((QPr->re_modifiers & QP_RE_MOD_GLOBAL) == QP_RE_MOD_GLOBAL) { + if (re_mod.length()) { + re_mod = re_mod + ","; + } + re_mod = re_mod + "GLOBAL"; + } + pta[12]=strdup_null((char *)re_mod.c_str()); // re_modifiers + itostr(pta[13], (long long)QPr->flagOUT); + pta[14]=strdup_null(QPr->replace_pattern); + itostr(pta[15], (long long)QPr->destination_hostgroup); + itostr(pta[16], (long long)QPr->cache_ttl); + itostr(pta[17], (long long)QPr->cache_empty_result); + itostr(pta[18], (long long)QPr->cache_timeout); + itostr(pta[19], (long long)QPr->reconnect); + itostr(pta[20], (long long)QPr->timeout); + itostr(pta[21], (long long)QPr->retries); + itostr(pta[22], (long long)QPr->delay); + itostr(pta[23], (long long)QPr->next_query_flagIN); + itostr(pta[24], (long long)QPr->mirror_flagOUT); + itostr(pta[25], (long long)QPr->mirror_hostgroup); + pta[26]=strdup_null(QPr->error_msg); + pta[27]=strdup_null(QPr->OK_msg); + itostr(pta[28], (long long)QPr->sticky_conn); + itostr(pta[29], (long long)QPr->multiplex); + itostr(pta[30], (long long)QPr->gtid_from_hostgroup); + itostr(pta[31], (long long)QPr->log); + itostr(pta[32], (long long)QPr->apply); + pta[33]=strdup_null(QPr->attributes); + pta[34]=strdup_null(QPr->comment); // issue #643 + itostr(pta[35], (long long)QPr->hits); +} + +QP_rule_text::~QP_rule_text() { + for(int i=0; i #include + +#include "QP_rule_text.h" + extern MySQL_Threads_Handler *GloMTH; extern ProxySQL_Admin *GloAdmin; @@ -41,279 +42,6 @@ static int int_cmp(const void *a, const void *b) { return 0; } -class QP_rule_text_hitsonly { - public: - char **pta; - QP_rule_text_hitsonly(QP_rule_t *QPr) { - pta=NULL; - pta=(char **)malloc(sizeof(char *)*2); - itostr(pta[0], (long long)QPr->rule_id); - itostr(pta[1], (long long)QPr->hits); - } - ~QP_rule_text_hitsonly() { - for(int i=0; i<2; i++) { - free_null(pta[i]); - } - free(pta); - } -}; - -class QP_rule_text { - public: - char **pta; - int num_fields; - QP_rule_text(QP_rule_t *QPr) { - num_fields=36; // this count the number of fields - pta=NULL; - pta=(char **)malloc(sizeof(char *)*num_fields); - itostr(pta[0], (long long)QPr->rule_id); - itostr(pta[1], (long long)QPr->active); - pta[2]=strdup_null(QPr->username); - pta[3]=strdup_null(QPr->schemaname); - itostr(pta[4], (long long)QPr->flagIN); - - pta[5]=strdup_null(QPr->client_addr); - pta[6]=strdup_null(QPr->proxy_addr); - itostr(pta[7], (long long)QPr->proxy_port); - - char buf[20]; - if (QPr->digest) { - sprintf(buf,"0x%016llX", (long long unsigned int)QPr->digest); - pta[8]=strdup(buf); - } else { - pta[8]=NULL; - } - - pta[9]=strdup_null(QPr->match_digest); - pta[10]=strdup_null(QPr->match_pattern); - itostr(pta[11], (long long)QPr->negate_match_pattern); - std::string re_mod; - re_mod=""; - if ((QPr->re_modifiers & QP_RE_MOD_CASELESS) == QP_RE_MOD_CASELESS) re_mod = "CASELESS"; - if ((QPr->re_modifiers & QP_RE_MOD_GLOBAL) == QP_RE_MOD_GLOBAL) { - if (re_mod.length()) { - re_mod = re_mod + ","; - } - re_mod = re_mod + "GLOBAL"; - } - pta[12]=strdup_null((char *)re_mod.c_str()); // re_modifiers - itostr(pta[13], (long long)QPr->flagOUT); - pta[14]=strdup_null(QPr->replace_pattern); - itostr(pta[15], (long long)QPr->destination_hostgroup); - itostr(pta[16], (long long)QPr->cache_ttl); - itostr(pta[17], (long long)QPr->cache_empty_result); - itostr(pta[18], (long long)QPr->cache_timeout); - itostr(pta[19], (long long)QPr->reconnect); - itostr(pta[20], (long long)QPr->timeout); - itostr(pta[21], (long long)QPr->retries); - itostr(pta[22], (long long)QPr->delay); - itostr(pta[23], (long long)QPr->next_query_flagIN); - itostr(pta[24], (long long)QPr->mirror_flagOUT); - itostr(pta[25], (long long)QPr->mirror_hostgroup); - pta[26]=strdup_null(QPr->error_msg); - pta[27]=strdup_null(QPr->OK_msg); - itostr(pta[28], (long long)QPr->sticky_conn); - itostr(pta[29], (long long)QPr->multiplex); - itostr(pta[30], (long long)QPr->gtid_from_hostgroup); - itostr(pta[31], (long long)QPr->log); - itostr(pta[32], (long long)QPr->apply); - pta[33]=strdup_null(QPr->attributes); - pta[34]=strdup_null(QPr->comment); // issue #643 - itostr(pta[35], (long long)QPr->hits); - } - ~QP_rule_text() { - for(int i=0; i 0); /* delete it */ - s[i] = '\0'; - reverse(s); -} - -QP_query_digest_stats::QP_query_digest_stats(char *u, char *s, uint64_t d, char *dt, int h, char *ca) { - digest=d; - digest_text=NULL; - if (dt) { - digest_text=strndup(dt, mysql_thread___query_digests_max_digest_length); - } - if (strlen(u) < sizeof(username_buf)) { - strcpy(username_buf,u); - username = username_buf; - } else { - username=strdup(u); - } - if (strlen(s) < sizeof(schemaname_buf)) { - strcpy(schemaname_buf,s); - schemaname = schemaname_buf; - } else { - schemaname=strdup(s); - } - if (strlen(ca) < sizeof(client_address_buf)) { - strcpy(client_address_buf,ca); - client_address = client_address_buf; - } else { - client_address=strdup(ca); - } - count_star=0; - first_seen=0; - last_seen=0; - sum_time=0; - min_time=0; - max_time=0; - rows_affected=0; - rows_sent=0; - hid=h; -} -void QP_query_digest_stats::add_time( - unsigned long long t, unsigned long long n, unsigned long long ra, unsigned long long rs, - unsigned long long cnt -) { - count_star += cnt; - sum_time+=t; - rows_affected+=ra; - rows_sent+=rs; - if (t < min_time || min_time==0) { - if (t) min_time = t; - } - if (t > max_time) { - max_time = t; - } - if (first_seen==0) { - first_seen=n; - } - last_seen=n; -} -QP_query_digest_stats::~QP_query_digest_stats() { - if (digest_text) { - free(digest_text); - digest_text=NULL; - } - if (username) { - if (username == username_buf) { - } else { - free(username); - } - username=NULL; - } - if (schemaname) { - if (schemaname == schemaname_buf) { - } else { - free(schemaname); - } - schemaname=NULL; - } - if (client_address) { - if (client_address == client_address_buf) { - } else { - free(client_address); - } - client_address=NULL; - } -} - -// Funtion to get the digest text associated to a QP_query_digest_stats. -// QP_query_digest_stats member type "char *digest_text" may by NULL, so we -// have to get the digest text from "digest_text_umap". -char *QP_query_digest_stats::get_digest_text(const umap_query_digest_text *digest_text_umap) { - char *digest_text_str = NULL; - - if (digest_text) { - digest_text_str = digest_text; - } else { - std::unordered_map::const_iterator it; - it = digest_text_umap->find(digest); - if (it != digest_text_umap->end()) { - digest_text_str = it->second; - } else { - // LCOV_EXCL_START - assert(0); - // LCOV_EXCL_STOP - } - } - - return digest_text_str; -} - -char **QP_query_digest_stats::get_row(umap_query_digest_text *digest_text_umap, query_digest_stats_pointers_t *qdsp) { - char **pta=qdsp->pta; - - assert(schemaname); - pta[0]=schemaname; - assert(username); - pta[1]=username; - assert(client_address); - pta[2]=client_address; - - assert(qdsp != NULL); - assert(qdsp->digest); - sprintf(qdsp->digest,"0x%016llX", (long long unsigned int)digest); - pta[3]=qdsp->digest; - - pta[4] = get_digest_text(digest_text_umap); - - //sprintf(qdsp->count_star,"%u",count_star); - my_itoa(qdsp->count_star, count_star); - pta[5]=qdsp->count_star; - - time_t __now; - time(&__now); - unsigned long long curtime=monotonic_time(); - time_t seen_time; - seen_time= __now - curtime/1000000 + first_seen/1000000; - //sprintf(qdsp->first_seen,"%ld", seen_time); - my_itoa(qdsp->first_seen, seen_time); - pta[6]=qdsp->first_seen; - - seen_time= __now - curtime/1000000 + last_seen/1000000; - //sprintf(qdsp->last_seen,"%ld", seen_time); - my_itoa(qdsp->last_seen, seen_time); - pta[7]=qdsp->last_seen; - //sprintf(qdsp->sum_time,"%llu",sum_time); - my_itoa(qdsp->sum_time,sum_time); - pta[8]=qdsp->sum_time; - //sprintf(qdsp->min_time,"%llu",min_time); - my_itoa(qdsp->min_time,min_time); - pta[9]=qdsp->min_time; - //sprintf(qdsp->max_time,"%llu",max_time); - my_itoa(qdsp->max_time,max_time); - pta[10]=qdsp->max_time; - // we are reverting this back to the use of sprintf instead of my_itoa - // because with my_itoa we are losing the sign - // see issue #2285 - sprintf(qdsp->hid,"%d",hid); - //my_itoa(qdsp->hid,hid); - pta[11]=qdsp->hid; - //sprintf(qdsp->rows_affected,"%llu",rows_affected); - my_itoa(qdsp->rows_affected,rows_affected); - pta[12]=qdsp->rows_affected; - //sprintf(qdsp->rows_sent,"%llu",rows_sent); - my_itoa(qdsp->rows_sent,rows_sent); - pta[13]=qdsp->rows_sent; - return pta; -} struct __RE2_objects_t { pcrecpp::RE_Options *opt1; diff --git a/lib/c_tokenizer.cpp b/lib/c_tokenizer.cpp index c468e0b185..5b35ef0fb3 100644 --- a/lib/c_tokenizer.cpp +++ b/lib/c_tokenizer.cpp @@ -1,6 +1,3 @@ -/* c_tokenizer.c */ -// Borrowed from http://www.cplusplus.com/faq/sequences/strings/split/ - #include #include #include @@ -234,591 +231,6 @@ static inline void replace_with_q_mark( } } -char *mysql_query_digest_and_first_comment(char *s, int _len, char **first_comment, char *buf){ - int i = 0; - - char cur_comment[FIRST_COMMENT_MAX_LENGTH]; - cur_comment[0]=0; - int ccl=0; - int cmd=0; - - int len = _len; - if (_len > mysql_thread___query_digests_max_query_length) { - len = mysql_thread___query_digests_max_query_length; - } - char *r = buf; - if (r==NULL) { - r = (char *) malloc(len + SIZECHAR); - } - char *p_r = r; - char *p_r_t = r; - - char prev_char = 0; - char qutr_char = 0; - - char flag = 0; - char fc=0; - int fc_len=0; - - char fns=0; - - bool lowercase=0; - bool replace_null=0; - bool replace_number=0; - - char grouping_digest=0; - char grouping_limit_exceeded=0; - int grouping_count=0; - int grouping_lim = mysql_thread___query_digests_grouping_limit; - - lowercase=mysql_thread___query_digests_lowercase; - replace_null = mysql_thread___query_digests_replace_null; - replace_number = mysql_thread___query_digests_no_digits; - - while(i < len) - { - // Handy for debugging purposes - // ============================ - // printf( - // "state-1: { flag: `%d`, prev_char: `%c`, s: `%s`, p_r: `%s`, r: `%s`}\n", - // flag, prev_char, s, p_r, r - // ); - // ============================ - - // ================================================= - // START - read token char and set flag what's going on. - // ================================================= - if(flag == 0) - { - // store current position - p_r_t = p_r; - - // comment type 1 - start with '/*' - if(prev_char == '/' && *s == '*') - { - ccl=0; - flag = 1; - if (i != (len-1) && *(s+1)=='!') - cmd=1; - } - - // comment type 2 - start with '#' - else if(*s == '#') - { - flag = 2; - } - - // comment type 3 - start with '--' - - // NOTE: Looks like the general rule for parsing comments of this type could simply be: - // - // - `.*--.*` which could be translated into `(*s == '-' && *(s+1) == '-')`. - // - // But this can not hold, since the first '-' could have been consumed previously, for example - // during the parsing of a digit: - // - // - `select 1.1-- final_comment\n` - // - // For this reason 'prev_char' needs to be checked too when searching for the `--` pattern. - else if(i != (len-1) && prev_char == '-' && *s == '-' && ((*(s+1)==' ') || (*(s+1)=='\n') || (*(s+1)=='\r') || (*(s+1)=='\t') )) - { - flag = 3; - } - - // Previous character can be a consumed ' ' instead of '-' as in the previous case, for this - // reason, we need to look ahead for '--'. - // - // NOTE: There is no reason for not checking for the subsequent space char that should follow - // the '-- ', otherwise we would consider valid queries as `SELECT --1` like comments. - else if (i != (len-1) && *s == '-' && (*(s+1)=='-')) { - if (prev_char != '-') { - flag = 3; - } - else if (i==0) { - flag = 3; - } - } - - // string - start with ' - else if(*s == '\'' || *s == '"') - { - flag = 4; - qutr_char = *s; - } - - // may be digit - start with digit - else if(is_token_char(prev_char) && is_digit_char(*s)) - { - flag = 5; - if(len == i+1) - continue; - } - - // not above case - remove duplicated space char - else - { - flag = 0; - if (fns==0 && is_space_char(*s)) { - s++; - i++; - continue; - } - if (fns==0) fns=1; - if(is_space_char(prev_char) && is_space_char(*s)){ - prev_char = ' '; - *p_r = ' '; - s++; - i++; - continue; - } - if (replace_number) { - if (!is_digit_char(prev_char) && is_digit_char(*s)) { - *p_r++ = '?'; - while(*s != '\0' && is_digit_char(*s)) { - s++; - i++; - } - } - } - { - char* p = p_r - 2; - // suppress spaces before arithmetic operators - if (p >= r && is_space_char(prev_char) && is_arithmetic_op(*s)) { - if (*p == '?') { - prev_char = *s; - --p_r; - *p_r++ = *s; - s++; - i++; - continue; - } - } - // suppress spaces before and after commas - if (p >= r && is_space_char(prev_char) && ((*s == ',') || (*p == ','))) { - if (*s == ',') { - --p_r; - // only copy the comma if we are not grouping a query - if (!grouping_limit_exceeded) { - *p_r++ = *s; - } - prev_char = ','; - s++; - i++; - } else { - prev_char = ','; - --p_r; - } - continue; - } - // suppress spaces before closing brackets when grouping or mark is present - if (p >= r && (*p == '.' || *p == '?') && is_space_char(prev_char) && (*s == ')')) { - prev_char = *s; - --p_r; - *p_r++ = *s; - s++; - i++; - continue; - } - } - if (replace_null) { - if (*s == 'n' || *s == 'N') { // we search for NULL , #2171 - if (i && is_token_char(prev_char)) { - if (len>=4) { - if (i=2) fc_len-=2; - char *c=*first_comment+fc_len; - *c=0; - //*first_comment[fc_len]=0; - fc=2; - } - } - } - if( - // comment type 1 - /* .. */ - (flag == 1 && prev_char == '*' && *s == '/') || - - // comment type 2 - # ... \n - (flag == 2 && (*s == '\n' || *s == '\r' || (i == len - 1) )) - || - // comment type 3 - -- ... \n - (flag == 3 && (*s == '\n' || *s == '\r' || (i == len -1) )) - ) - { - p_r = p_r_t; - if (flag == 1 || (i == len -1)) { - p_r -= SIZECHAR; - } - if (cmd) { - cur_comment[ccl]=0; - if (ccl>=2) { - ccl-=2; - cur_comment[ccl]=0; - char el=0; - int fcc=0; - while (el==0 && fcc= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { - if ( - ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || - ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) - ) { - p_r--; - } - } - - replace_with_q_mark( - grouping_digest, grouping_lim, &grouping_count, &p_r, &grouping_limit_exceeded - ); - - flag = 0; - break; - } - - // need to be ignored case - if(p_r > p_r_t + SIZECHAR) - { - if( - (prev_char == '\\' && *s == '\\') || // to process '\\\\', '\\' - (prev_char == '\\' && *s == qutr_char) || // to process '\'' - (prev_char == qutr_char && *s == qutr_char) // to process '''' - ) - { - prev_char = 'X'; - s++; - i++; - continue; - } - } - - // satisfied closing string - swap string to ? - if(*s == qutr_char && (len == i+1 || *(s + SIZECHAR) != qutr_char)) - { - char *_p = p_r_t; - _p-=3; - p_r = p_r_t; - if ( _p >= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { - if ( - ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || - ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) - ) { - p_r--; - } - } - - // Remove spaces before each literal found - if ( _p >= r && is_space_char(*(_p + 2)) && !is_normal_char(*(_p + 1))) { - if ( _p >= r && ( *(_p+3) == '\''|| *(_p+3) == '"' )) { - p_r--; - } - } - - replace_with_q_mark( - grouping_digest, grouping_lim, &grouping_count, &p_r, &grouping_limit_exceeded - ); - - prev_char = qutr_char; - qutr_char = 0; - flag = 0; - if(i < len) - s++; - i++; - continue; - } - } - - // -------- - // digit - // -------- - else if(flag == 5) - { - // last single char - if(p_r_t == p_r) - { - char *_p = p_r_t; - _p-=3; - if ( _p >= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { - if ( - ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || - ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) - ) { - p_r--; - } - } - if ( _p >= r && is_space_char(*(_p + 2))) { - if ( _p >= r && ( *(_p+1) == '-' || *(_p+1) == '+' || *(_p+1) == '*' || *(_p+1) == '/' || *(_p+1) == '%' || *(_p+1) == ',')) { - p_r--; - } - } - *p_r++ = '?'; - i++; - continue; - } - - // is float - if (*s == '.' || *s == 'e' || ((*s == '+' || *s == '-') && prev_char == 'e')) { - prev_char = *s; - i++; - s++; - continue; - } - - // token char or last char - if(is_token_char(*s) || len == i+1) - { - if(is_digit_string(p_r_t, p_r)) - { - char *_p = p_r_t; - _p-=3; - p_r = p_r_t; - // remove symbol and keep parenthesis or comma - if ( _p >= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { - if ( - ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || - ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) - ) { - p_r--; - } - } - - // Remove spaces before number counting with possible '.' presence - if (_p >= r && *_p == '.' && (*(_p + 1) == ' ' || *(_p + 1) == '.') && (*(_p+2) == '-' || *(_p+2) == '+') ) { - if (*(_p + 1) == ' ') { - p_r--; - } - p_r--; - } - - // Remove spaces after a opening bracket when followed by a number - if (_p >= r && *(_p+1) == '(' && *(_p+2) == ' ') { - p_r--; - } - - // Remove spaces before number - if ( _p >= r && is_space_char(*(_p + 2))) { - // A point can be found prior to a number in case of query grouping - if ( _p >= r && ( *(_p+1) == '-' || *(_p+1) == '+' || *(_p+1) == '*' || *(_p+1) == '/' || *(_p+1) == '%' || *(_p+1) == ',' || *(_p+1) == '.')) { - p_r--; - } - } - - replace_with_q_mark( - grouping_digest, grouping_lim, &grouping_count, &p_r, &grouping_limit_exceeded - ); - - if(len == i+1) - { - if(is_token_char(*s)) - *p_r++ = *s; - i++; - continue; - } - } else { - // collapse any digits found in the string - if (replace_number) { - int str_len = p_r - p_r_t + 1; - int collapsed = 0; - - for (int j = 0; j < str_len; j++) { - char* const c_p_r_t = ((char*)p_r_t + j); - char* const n_p_r_t = ((char*)p_r_t + j + 1); - - if (is_digit_char(*c_p_r_t) && is_digit_char(*n_p_r_t)) { - memmove(c_p_r_t, c_p_r_t + 1, str_len - j); - collapsed += 1; - } - } - - p_r -= collapsed; - - int new_str_len = p_r - p_r_t + 1; - for (int j = 0; j < new_str_len; j++) { - char* const c_p_r_t = ((char*)p_r_t + j); - if (is_digit_char(*c_p_r_t)) { - *c_p_r_t = '?'; - } - } - } - } - - flag = 0; - } - } - } - - // ================================================= - // COPY CHAR - // ================================================= - // convert every space char to ' ' - if (*s == ')') { - if (grouping_digest > 0) { - grouping_digest -= 1; - }; - grouping_count = 0; - grouping_limit_exceeded = 0; - } - - if (lowercase==0) { - *p_r++ = !is_space_char(*s) ? *s : ' '; - } else { - *p_r++ = !is_space_char(*s) ? (tolower(*s)) : ' '; - } - - if (*s == '(') { - grouping_digest += 1; - grouping_count = 0; - grouping_limit_exceeded = 0; - } - - prev_char = *s++; - - i++; - } - - // remove a trailing space - if (p_r>r) { - char *e=p_r; - e--; - if (*e==' ') { - *e=0; - // maybe 2 trailing spaces . It happens with comments - e--; - if (*e==' ') { - *e=0; - } - } - } - - *p_r = 0; - - // process query stats - return r; -} - /** * @brief Struct for holding all the configuration options used for query digests generation. */ diff --git a/lib/c_tokenizer_legacy.cpp b/lib/c_tokenizer_legacy.cpp new file mode 100644 index 0000000000..ae93027afe --- /dev/null +++ b/lib/c_tokenizer_legacy.cpp @@ -0,0 +1,588 @@ +/* + this file is here only for reference. + It includes the old mysql_query_digest_and_first_comment() , outdated since ProxySQL 2.4.0 +*/ +char *mysql_query_digest_and_first_comment(char *s, int _len, char **first_comment, char *buf){ + int i = 0; + + char cur_comment[FIRST_COMMENT_MAX_LENGTH]; + cur_comment[0]=0; + int ccl=0; + int cmd=0; + + int len = _len; + if (_len > mysql_thread___query_digests_max_query_length) { + len = mysql_thread___query_digests_max_query_length; + } + char *r = buf; + if (r==NULL) { + r = (char *) malloc(len + SIZECHAR); + } + char *p_r = r; + char *p_r_t = r; + + char prev_char = 0; + char qutr_char = 0; + + char flag = 0; + char fc=0; + int fc_len=0; + + char fns=0; + + bool lowercase=0; + bool replace_null=0; + bool replace_number=0; + + char grouping_digest=0; + char grouping_limit_exceeded=0; + int grouping_count=0; + int grouping_lim = mysql_thread___query_digests_grouping_limit; + + lowercase=mysql_thread___query_digests_lowercase; + replace_null = mysql_thread___query_digests_replace_null; + replace_number = mysql_thread___query_digests_no_digits; + + while(i < len) + { + // Handy for debugging purposes + // ============================ + // printf( + // "state-1: { flag: `%d`, prev_char: `%c`, s: `%s`, p_r: `%s`, r: `%s`}\n", + // flag, prev_char, s, p_r, r + // ); + // ============================ + + // ================================================= + // START - read token char and set flag what's going on. + // ================================================= + if(flag == 0) + { + // store current position + p_r_t = p_r; + + // comment type 1 - start with '/*' + if(prev_char == '/' && *s == '*') + { + ccl=0; + flag = 1; + if (i != (len-1) && *(s+1)=='!') + cmd=1; + } + + // comment type 2 - start with '#' + else if(*s == '#') + { + flag = 2; + } + + // comment type 3 - start with '--' + + // NOTE: Looks like the general rule for parsing comments of this type could simply be: + // + // - `.*--.*` which could be translated into `(*s == '-' && *(s+1) == '-')`. + // + // But this can not hold, since the first '-' could have been consumed previously, for example + // during the parsing of a digit: + // + // - `select 1.1-- final_comment\n` + // + // For this reason 'prev_char' needs to be checked too when searching for the `--` pattern. + else if(i != (len-1) && prev_char == '-' && *s == '-' && ((*(s+1)==' ') || (*(s+1)=='\n') || (*(s+1)=='\r') || (*(s+1)=='\t') )) + { + flag = 3; + } + + // Previous character can be a consumed ' ' instead of '-' as in the previous case, for this + // reason, we need to look ahead for '--'. + // + // NOTE: There is no reason for not checking for the subsequent space char that should follow + // the '-- ', otherwise we would consider valid queries as `SELECT --1` like comments. + else if (i != (len-1) && *s == '-' && (*(s+1)=='-')) { + if (prev_char != '-') { + flag = 3; + } + else if (i==0) { + flag = 3; + } + } + + // string - start with ' + else if(*s == '\'' || *s == '"') + { + flag = 4; + qutr_char = *s; + } + + // may be digit - start with digit + else if(is_token_char(prev_char) && is_digit_char(*s)) + { + flag = 5; + if(len == i+1) + continue; + } + + // not above case - remove duplicated space char + else + { + flag = 0; + if (fns==0 && is_space_char(*s)) { + s++; + i++; + continue; + } + if (fns==0) fns=1; + if(is_space_char(prev_char) && is_space_char(*s)){ + prev_char = ' '; + *p_r = ' '; + s++; + i++; + continue; + } + if (replace_number) { + if (!is_digit_char(prev_char) && is_digit_char(*s)) { + *p_r++ = '?'; + while(*s != '\0' && is_digit_char(*s)) { + s++; + i++; + } + } + } + { + char* p = p_r - 2; + // suppress spaces before arithmetic operators + if (p >= r && is_space_char(prev_char) && is_arithmetic_op(*s)) { + if (*p == '?') { + prev_char = *s; + --p_r; + *p_r++ = *s; + s++; + i++; + continue; + } + } + // suppress spaces before and after commas + if (p >= r && is_space_char(prev_char) && ((*s == ',') || (*p == ','))) { + if (*s == ',') { + --p_r; + // only copy the comma if we are not grouping a query + if (!grouping_limit_exceeded) { + *p_r++ = *s; + } + prev_char = ','; + s++; + i++; + } else { + prev_char = ','; + --p_r; + } + continue; + } + // suppress spaces before closing brackets when grouping or mark is present + if (p >= r && (*p == '.' || *p == '?') && is_space_char(prev_char) && (*s == ')')) { + prev_char = *s; + --p_r; + *p_r++ = *s; + s++; + i++; + continue; + } + } + if (replace_null) { + if (*s == 'n' || *s == 'N') { // we search for NULL , #2171 + if (i && is_token_char(prev_char)) { + if (len>=4) { + if (i=2) fc_len-=2; + char *c=*first_comment+fc_len; + *c=0; + //*first_comment[fc_len]=0; + fc=2; + } + } + } + if( + // comment type 1 - /* .. */ + (flag == 1 && prev_char == '*' && *s == '/') || + + // comment type 2 - # ... \n + (flag == 2 && (*s == '\n' || *s == '\r' || (i == len - 1) )) + || + // comment type 3 - -- ... \n + (flag == 3 && (*s == '\n' || *s == '\r' || (i == len -1) )) + ) + { + p_r = p_r_t; + if (flag == 1 || (i == len -1)) { + p_r -= SIZECHAR; + } + if (cmd) { + cur_comment[ccl]=0; + if (ccl>=2) { + ccl-=2; + cur_comment[ccl]=0; + char el=0; + int fcc=0; + while (el==0 && fcc= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { + if ( + ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || + ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) + ) { + p_r--; + } + } + + replace_with_q_mark( + grouping_digest, grouping_lim, &grouping_count, &p_r, &grouping_limit_exceeded + ); + + flag = 0; + break; + } + + // need to be ignored case + if(p_r > p_r_t + SIZECHAR) + { + if( + (prev_char == '\\' && *s == '\\') || // to process '\\\\', '\\' + (prev_char == '\\' && *s == qutr_char) || // to process '\'' + (prev_char == qutr_char && *s == qutr_char) // to process '''' + ) + { + prev_char = 'X'; + s++; + i++; + continue; + } + } + + // satisfied closing string - swap string to ? + if(*s == qutr_char && (len == i+1 || *(s + SIZECHAR) != qutr_char)) + { + char *_p = p_r_t; + _p-=3; + p_r = p_r_t; + if ( _p >= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { + if ( + ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || + ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) + ) { + p_r--; + } + } + + // Remove spaces before each literal found + if ( _p >= r && is_space_char(*(_p + 2)) && !is_normal_char(*(_p + 1))) { + if ( _p >= r && ( *(_p+3) == '\''|| *(_p+3) == '"' )) { + p_r--; + } + } + + replace_with_q_mark( + grouping_digest, grouping_lim, &grouping_count, &p_r, &grouping_limit_exceeded + ); + + prev_char = qutr_char; + qutr_char = 0; + flag = 0; + if(i < len) + s++; + i++; + continue; + } + } + + // -------- + // digit + // -------- + else if(flag == 5) + { + // last single char + if(p_r_t == p_r) + { + char *_p = p_r_t; + _p-=3; + if ( _p >= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { + if ( + ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || + ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) + ) { + p_r--; + } + } + if ( _p >= r && is_space_char(*(_p + 2))) { + if ( _p >= r && ( *(_p+1) == '-' || *(_p+1) == '+' || *(_p+1) == '*' || *(_p+1) == '/' || *(_p+1) == '%' || *(_p+1) == ',')) { + p_r--; + } + } + *p_r++ = '?'; + i++; + continue; + } + + // is float + if (*s == '.' || *s == 'e' || ((*s == '+' || *s == '-') && prev_char == 'e')) { + prev_char = *s; + i++; + s++; + continue; + } + + // token char or last char + if(is_token_char(*s) || len == i+1) + { + if(is_digit_string(p_r_t, p_r)) + { + char *_p = p_r_t; + _p-=3; + p_r = p_r_t; + // remove symbol and keep parenthesis or comma + if ( _p >= r && ( *(_p+2) == '-' || *(_p+2) == '+') ) { + if ( + ( *(_p+1) == ',' ) || ( *(_p+1) == '(' ) || + ( ( *(_p+1) == ' ' ) && ( *_p == ',' || *_p == '(' ) ) + ) { + p_r--; + } + } + + // Remove spaces before number counting with possible '.' presence + if (_p >= r && *_p == '.' && (*(_p + 1) == ' ' || *(_p + 1) == '.') && (*(_p+2) == '-' || *(_p+2) == '+') ) { + if (*(_p + 1) == ' ') { + p_r--; + } + p_r--; + } + + // Remove spaces after a opening bracket when followed by a number + if (_p >= r && *(_p+1) == '(' && *(_p+2) == ' ') { + p_r--; + } + + // Remove spaces before number + if ( _p >= r && is_space_char(*(_p + 2))) { + // A point can be found prior to a number in case of query grouping + if ( _p >= r && ( *(_p+1) == '-' || *(_p+1) == '+' || *(_p+1) == '*' || *(_p+1) == '/' || *(_p+1) == '%' || *(_p+1) == ',' || *(_p+1) == '.')) { + p_r--; + } + } + + replace_with_q_mark( + grouping_digest, grouping_lim, &grouping_count, &p_r, &grouping_limit_exceeded + ); + + if(len == i+1) + { + if(is_token_char(*s)) + *p_r++ = *s; + i++; + continue; + } + } else { + // collapse any digits found in the string + if (replace_number) { + int str_len = p_r - p_r_t + 1; + int collapsed = 0; + + for (int j = 0; j < str_len; j++) { + char* const c_p_r_t = ((char*)p_r_t + j); + char* const n_p_r_t = ((char*)p_r_t + j + 1); + + if (is_digit_char(*c_p_r_t) && is_digit_char(*n_p_r_t)) { + memmove(c_p_r_t, c_p_r_t + 1, str_len - j); + collapsed += 1; + } + } + + p_r -= collapsed; + + int new_str_len = p_r - p_r_t + 1; + for (int j = 0; j < new_str_len; j++) { + char* const c_p_r_t = ((char*)p_r_t + j); + if (is_digit_char(*c_p_r_t)) { + *c_p_r_t = '?'; + } + } + } + } + + flag = 0; + } + } + } + + // ================================================= + // COPY CHAR + // ================================================= + // convert every space char to ' ' + if (*s == ')') { + if (grouping_digest > 0) { + grouping_digest -= 1; + }; + grouping_count = 0; + grouping_limit_exceeded = 0; + } + + if (lowercase==0) { + *p_r++ = !is_space_char(*s) ? *s : ' '; + } else { + *p_r++ = !is_space_char(*s) ? (tolower(*s)) : ' '; + } + + if (*s == '(') { + grouping_digest += 1; + grouping_count = 0; + grouping_limit_exceeded = 0; + } + + prev_char = *s++; + + i++; + } + + // remove a trailing space + if (p_r>r) { + char *e=p_r; + e--; + if (*e==' ') { + *e=0; + // maybe 2 trailing spaces . It happens with comments + e--; + if (*e==' ') { + *e=0; + } + } + } + + *p_r = 0; + + // process query stats + return r; +} diff --git a/lib/debug.cpp b/lib/debug.cpp index 7d236f6a0f..e4eda648f5 100644 --- a/lib/debug.cpp +++ b/lib/debug.cpp @@ -46,24 +46,28 @@ static inline unsigned long long debug_monotonic_time() { #ifdef DEBUG -// this set will have all the filters related to debug -// for convention, the key is: -// filename:line:function -// this key structure applies also if line is 0 or function is empty -// filename is mandatory -std::set debug_filters; +/** + * @brief Contains all filters related to debug. + * @details The convention for key value is `filename:line:function`. This key structure also applies also + * applies if the line is `0` or function is empty, the `filename` is always mandatory. + * + * IMPORTANT: This structure is a pointer to avoid race conditions during process termination, otherwise the + * destruction of the object may be performed before working threads have exited. This structure will leak, + * this is intentional, since we can't synchronize the exit of the working threads with its destruction. + */ +std::set* debug_filters = nullptr; static bool filter_debug_entry(const char *__file, int __line, const char *__func) { //pthread_mutex_lock(&debug_mutex); pthread_rwlock_rdlock(&filters_rwlock); bool to_filter = false; - if (debug_filters.size()) { // if the set is empty we aren't performing any filter, so we won't search + if (debug_filters && debug_filters->size()) { // if the set is empty we aren't performing any filter, so we won't search std::string key(__file); key += ":" + std::to_string(__line); key += ":"; key += __func; // we start with a full search - if (debug_filters.find(key) != debug_filters.end()) { + if (debug_filters->find(key) != debug_filters->end()) { to_filter = true; } else { // we now search filename + line @@ -71,7 +75,7 @@ static bool filter_debug_entry(const char *__file, int __line, const char *__fun key += ":" + std::to_string(__line); // remember to add the final ":" key += ":"; - if (debug_filters.find(key) != debug_filters.end()) { + if (debug_filters->find(key) != debug_filters->end()) { to_filter = true; } else { // we now search filename + function @@ -79,14 +83,14 @@ static bool filter_debug_entry(const char *__file, int __line, const char *__fun // no line = 0 key += ":0:"; key += __func; - if (debug_filters.find(key) != debug_filters.end()) { + if (debug_filters->find(key) != debug_filters->end()) { to_filter = true; } else { // we now search filename only key = __file; // remember to add ":" even if no line key += ":0:"; - if (debug_filters.find(key) != debug_filters.end()) { + if (debug_filters->find(key) != debug_filters->end()) { to_filter = true; } else { // if we reached here, we couldn't find any filter @@ -105,7 +109,9 @@ static bool filter_debug_entry(const char *__file, int __line, const char *__fun void proxy_debug_get_filters(std::set& f) { //pthread_mutex_lock(&debug_mutex); pthread_rwlock_rdlock(&filters_rwlock); - f = debug_filters; + if (debug_filters) { + f = *debug_filters; + } pthread_rwlock_unlock(&filters_rwlock); //pthread_mutex_unlock(&debug_mutex); } @@ -115,8 +121,12 @@ void proxy_debug_get_filters(std::set& f) { void proxy_debug_load_filters(std::set& f) { //pthread_mutex_lock(&debug_mutex); pthread_rwlock_wrlock(&filters_rwlock); - debug_filters.erase(debug_filters.begin(), debug_filters.end()); - debug_filters = f; + if (debug_filters) { + debug_filters->erase(debug_filters->begin(), debug_filters->end()); + *debug_filters = f; + } else { + debug_filters = new std::set(f); + } pthread_rwlock_unlock(&filters_rwlock); //pthread_mutex_unlock(&debug_mutex); } diff --git a/lib/mysql_connection.cpp b/lib/mysql_connection.cpp index 37c24b2db2..53b47cc7c4 100644 --- a/lib/mysql_connection.cpp +++ b/lib/mysql_connection.cpp @@ -109,7 +109,7 @@ extern char * binary_sha1; #include "proxysql_find_charset.h" -void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { +void Variable::fill_server_internal_session(json &j, int idx) { if (idx == SQL_CHARACTER_SET_RESULTS || idx == SQL_CHARACTER_SET_CLIENT || idx == SQL_CHARACTER_SET_DATABASE) { const MARIADB_CHARSET_INFO *ci = NULL; if (!value) { @@ -120,9 +120,9 @@ void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { if (!ci) { if (idx == SQL_CHARACTER_SET_RESULTS && (!strcasecmp("NULL", value) || !strcasecmp("binary", value))) { if (!strcasecmp("NULL", value)) { - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = ""; + j[mysql_tracked_variables[idx].internal_variable_name] = ""; } else { - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = value; + j[mysql_tracked_variables[idx].internal_variable_name] = value; } } else { // LCOV_EXCL_START @@ -131,7 +131,7 @@ void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { // LCOV_EXCL_STOP } } else { - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->csname)?ci->csname:""); + j[mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->csname)?ci->csname:""); } } else if (idx == SQL_CHARACTER_SET_CONNECTION) { const MARIADB_CHARSET_INFO *ci = NULL; @@ -140,7 +140,7 @@ void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { else ci = proxysql_find_charset_nr(atoi(value)); - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->csname)?ci->csname:""); + j[mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->csname)?ci->csname:""); } else if (idx == SQL_COLLATION_CONNECTION) { const MARIADB_CHARSET_INFO *ci = NULL; if (!value) @@ -148,7 +148,7 @@ void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { else ci = proxysql_find_charset_nr(atoi(value)); - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->name)?ci->name:""); + j[mysql_tracked_variables[idx].internal_variable_name] = std::string((ci && ci->name)?ci->name:""); /* // NOTE: it seems we treat SQL_LOG_BIN in a special way // it doesn't seem necessary @@ -159,7 +159,7 @@ void Variable::fill_server_internal_session(json &j, int conn_num, int idx) { j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string(!strcmp("1",value)?"ON":"OFF"); */ } else { - j["backends"][conn_num]["conn"][mysql_tracked_variables[idx].internal_variable_name] = std::string(value?value:""); + j[mysql_tracked_variables[idx].internal_variable_name] = std::string(value?value:""); } } @@ -174,9 +174,9 @@ void Variable::fill_client_internal_session(json &j, int idx) { if (!ci) { if (idx == SQL_CHARACTER_SET_RESULTS && (!strcasecmp("NULL", value) || !strcasecmp("binary", value))) { if (!strcasecmp("NULL", value)) { - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = ""; + j[mysql_tracked_variables[idx].internal_variable_name] = ""; } else { - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = value; + j[mysql_tracked_variables[idx].internal_variable_name] = value; } } else { // LCOV_EXCL_START @@ -185,7 +185,7 @@ void Variable::fill_client_internal_session(json &j, int idx) { // LCOV_EXCL_STOP } } else { - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->csname)?ci->csname:""; + j[mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->csname)?ci->csname:""; } } else if (idx == SQL_CHARACTER_SET_CONNECTION) { const MARIADB_CHARSET_INFO *ci = NULL; @@ -193,14 +193,14 @@ void Variable::fill_client_internal_session(json &j, int idx) { ci = proxysql_find_charset_collate(mysql_tracked_variables[idx].default_value); else ci = proxysql_find_charset_nr(atoi(value)); - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->csname)?ci->csname:""; + j[mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->csname)?ci->csname:""; } else if (idx == SQL_COLLATION_CONNECTION) { const MARIADB_CHARSET_INFO *ci = NULL; if (!value) ci = proxysql_find_charset_collate(mysql_tracked_variables[idx].default_value); else ci = proxysql_find_charset_nr(atoi(value)); - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->name)?ci->name:""; + j[mysql_tracked_variables[idx].internal_variable_name] = (ci && ci->name)?ci->name:""; /* // NOTE: it seems we treat SQL_LOG_BIN in a special way // it doesn't seem necessary @@ -211,7 +211,7 @@ void Variable::fill_client_internal_session(json &j, int idx) { j["conn"][mysql_tracked_variables[idx].internal_variable_name] = !strcmp("1", value)?"ON":"OFF"; */ } else { - j["conn"][mysql_tracked_variables[idx].internal_variable_name] = value?value:""; + j[mysql_tracked_variables[idx].internal_variable_name] = value?value:""; } } @@ -740,12 +740,7 @@ bool MySQL_Connection::match_tracked_options(const MySQL_Connection *c) { return false; } -// non blocking API -void MySQL_Connection::connect_start() { - PROXY_TRACE(); - mysql=mysql_init(NULL); - assert(mysql); - mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); +void MySQL_Connection::connect_start_SetAttributes() { mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "proxysql"); mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "_server_host", parent->address); { @@ -770,18 +765,10 @@ void MySQL_Connection::connect_start() { } mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "mysql_bug_102266", "Avoid MySQL bug https://bugs.mysql.com/bug.php?id=102266 , https://github.com/sysown/proxysql/issues/3276"); } - if (parent->use_ssl) { - if (ssl_params != NULL) { - delete ssl_params; - ssl_params = NULL; - } - ssl_params = MyHGM->get_Server_SSL_Params(parent->address, parent->port, userinfo->username); - MySQL_Connection::set_ssl_params(mysql, ssl_params); - mysql_options(mysql, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); - } - unsigned int timeout= 1; +} + +void MySQL_Connection::connect_start_SetCharset() { const char *csname = NULL; - mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout); /* Take client character set and use it to connect to backend */ if (myds && myds->sess) { csname = mysql_variables.client_get_value(myds->sess, SQL_CHARACTER_SET); @@ -815,7 +802,10 @@ void MySQL_Connection::connect_start() { } //mysql_options(mysql, MYSQL_SET_CHARSET_NAME, c->csname); mysql->charset = c; - unsigned long client_flags = 0; +} + +void MySQL_Connection::connect_start_SetClientFlag(unsigned long& client_flags) { + client_flags = 0; if (parent->compression) client_flags |= CLIENT_COMPRESS; @@ -874,46 +864,82 @@ void MySQL_Connection::connect_start() { } } - char *auth_password=NULL; - if (userinfo->password) { - if (userinfo->password[0]=='*') { // we don't have the real password, let's pass sha1 - auth_password=userinfo->sha1_pass; - } else { - auth_password=userinfo->password; - } - } - if (parent->port) { +} - char* host_ip = NULL; - const std::string& res_ip = MySQL_Monitor::dns_lookup(parent->address, false); +char * MySQL_Connection::connect_start_DNS_lookup() { + char* host_ip = NULL; + const std::string& res_ip = MySQL_Monitor::dns_lookup(parent->address, false); - if (!res_ip.empty()) { - if (connected_host_details.hostname) { - if (strcmp(connected_host_details.hostname, parent->address) != 0) { - free(connected_host_details.hostname); - connected_host_details.hostname = strdup(parent->address); - } - } - else { + if (!res_ip.empty()) { + if (connected_host_details.hostname) { + if (strcmp(connected_host_details.hostname, parent->address) != 0) { + free(connected_host_details.hostname); connected_host_details.hostname = strdup(parent->address); } + } + else { + connected_host_details.hostname = strdup(parent->address); + } - if (connected_host_details.ip) { - if (strcmp(connected_host_details.ip, res_ip.c_str()) != 0) { - free(connected_host_details.ip); - connected_host_details.ip = strdup(res_ip.c_str()); - } - } - else { + if (connected_host_details.ip) { + if (strcmp(connected_host_details.ip, res_ip.c_str()) != 0) { + free(connected_host_details.ip); connected_host_details.ip = strdup(res_ip.c_str()); } - - host_ip = connected_host_details.ip; } else { - host_ip = parent->address; + connected_host_details.ip = strdup(res_ip.c_str()); } + host_ip = connected_host_details.ip; + } + else { + host_ip = parent->address; + } + return host_ip; +} + +void MySQL_Connection::connect_start_SetSslSettings() { + if (parent->use_ssl) { + if (ssl_params != NULL) { + delete ssl_params; + ssl_params = NULL; + } + ssl_params = MyHGM->get_Server_SSL_Params(parent->address, parent->port, userinfo->username); + MySQL_Connection::set_ssl_params(mysql, ssl_params); + mysql_options(mysql, MARIADB_OPT_SSL_KEYLOG_CALLBACK, (void*)proxysql_keylog_write_line_callback); + } +} + +// non blocking API +void MySQL_Connection::connect_start() { + PROXY_TRACE(); + mysql=mysql_init(NULL); + assert(mysql); + mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); + + connect_start_SetAttributes(); + + connect_start_SetSslSettings(); + + unsigned int timeout= 1; + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (void *)&timeout); + + connect_start_SetCharset(); + + unsigned long client_flags = 0; + connect_start_SetClientFlag(client_flags); + + char *auth_password=NULL; + if (userinfo->password) { + if (userinfo->password[0]=='*') { // we don't have the real password, let's pass sha1 + auth_password=userinfo->sha1_pass; + } else { + auth_password=userinfo->password; + } + } + if (parent->port) { + char* host_ip = connect_start_DNS_lookup(); async_exit_status=mysql_real_connect_start(&ret_mysql, mysql, host_ip, userinfo->username, auth_password, userinfo->schemaname, parent->port, NULL, client_flags); } else { client_flags &= ~(CLIENT_COMPRESS); // disabling compression for connections made via Unix socket @@ -1721,7 +1747,7 @@ MDB_ASYNC_ST MySQL_Connection::handler(short event) { // since 'add_eof' utilizes 'warning_count,' we are setting the 'warning_count' here update_warning_count_from_connection(); // we reach here if there was no error - // exclude warning_count from the OK/EOF packet for the ‘SHOW WARNINGS’ statement + // exclude warning_count from the OK/EOF packet for the ‘SHOW WARNINGSÂ’ statement MyRS->add_eof(query.length == 13 && strncasecmp(query.ptr, "SHOW WARNINGS", 13) == 0); NEXT_IMMEDIATE(ASYNC_QUERY_END); } @@ -2035,37 +2061,63 @@ bool MySQL_Connection::IsServerOffline() { bool ret=false; if (parent==NULL) return ret; - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if ( (server_status==MYSQL_SERVER_STATUS_OFFLINE_HARD) // the server is OFFLINE as specific by the user || (server_status==MYSQL_SERVER_STATUS_SHUNNED && parent->shunned_automatic==true && parent->shunned_and_kill_all_connections==true) // the server is SHUNNED due to a serious issue || - (server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) // slave is lagging! see #774 + (server_status==MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) // slave is lagging! see #774 + || + (parent->myhgc->online_servers_within_threshold() == false) // number of online servers in a hostgroup exceeds the configured maximum servers ) { ret=true; } return ret; } -// Returns: -// 0 when the query is completed -// 1 when the query is not completed -// the calling function should check mysql error in mysql struct +/** + * @brief Asynchronously execute a query on the MySQL connection. + * + * This function asynchronously executes a query on the MySQL connection. + * It handles various states of the asynchronous query execution process + * and returns appropriate status codes indicating the result of the execution. + * + * @param event The event associated with the query execution. + * @param stmt The query statement to be executed. + * @param length The length of the query statement. + * @param _stmt Pointer to the MySQL statement handle. + * @param stmt_meta Metadata associated with the statement execution. + * + * @return Returns an integer status code indicating the result of the query execution: + * - 0: Query execution completed successfully. + * - -1: Query execution failed. + * - 1: Query execution in progress. + * - 2: Processing a multi-statement query, control needs to be transferred to MySQL_Session. + * - 3: In the middle of processing a multi-statement query. + */ int MySQL_Connection::async_query(short event, char *stmt, unsigned long length, MYSQL_STMT **_stmt, stmt_execute_metadata_t *stmt_meta) { + // Trace the entry of the function PROXY_TRACE(); PROXY_TRACE2(); + + // Ensure MySQL connection is valid assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this + + // Check if server is offline if (IsServerOffline()) return -1; + // Update DSS state if myds is available if (myds) { if (myds->DSS != STATE_MARIADB_QUERY) { myds->DSS = STATE_MARIADB_QUERY; } } + + // Handle different states of async query execution switch (async_state_machine) { case ASYNC_QUERY_END: processing_multi_statement=false; // no matter if we are processing a multi statement or not, we reached the end @@ -2099,6 +2151,8 @@ int MySQL_Connection::async_query(short event, char *stmt, unsigned long length, break; } + // Handle different states after async query execution. + // That means after hander() was executed. if (async_state_machine==ASYNC_QUERY_END) { PROXY_TRACE2(); compute_unknown_transaction_status(); @@ -2196,7 +2250,7 @@ int MySQL_Connection::async_change_user(short event) { PROXY_TRACE(); assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if (IsServerOffline()) return -1; @@ -2243,7 +2297,7 @@ int MySQL_Connection::async_select_db(short event) { PROXY_TRACE(); assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if (IsServerOffline()) return -1; @@ -2284,7 +2338,7 @@ int MySQL_Connection::async_set_autocommit(short event, bool ac) { PROXY_TRACE(); assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if (IsServerOffline()) return -1; @@ -2327,7 +2381,7 @@ int MySQL_Connection::async_set_names(short event, unsigned int c) { PROXY_TRACE(); assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if (IsServerOffline()) return -1; @@ -2370,7 +2424,7 @@ int MySQL_Connection::async_set_option(short event, bool mask) { PROXY_TRACE(); assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if (IsServerOffline()) return -1; @@ -2643,24 +2697,7 @@ bool MySQL_Connection::IsKeepMultiplexEnabledVariables(char *query_digest_text) return true; } -void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { - if (query_digest_text==NULL) return; - // unknown what to do with multiplex - int mul=-1; - if (myds) { - if (myds->sess) { - if (myds->sess->qpo) { - mul=myds->sess->qpo->multiplex; - if (mul==0) { - set_status(true, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX); - } else { - if (mul==1) { - set_status(false, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX); - } - } - } - } - } +void MySQL_Connection::ProcessQueryAndSetStatusFlags_Warnings(char *query_digest_text) { // checking warnings and disabling multiplexing will be effective only when the mysql-query_digests is enabled if (get_status(STATUS_MYSQL_CONNECTION_HAS_WARNINGS) == false) { if (warning_count > 0) { @@ -2686,7 +2723,10 @@ void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { set_status(false, STATUS_MYSQL_CONNECTION_HAS_WARNINGS); } } - +} + + +void MySQL_Connection::ProcessQueryAndSetStatusFlags_UserVariables(char *query_digest_text, int mul) { if (get_status(STATUS_MYSQL_CONNECTION_USER_VARIABLE)==false) { // we search for variables only if not already set // if ( // strncasecmp(query_digest_text,"SELECT @@tx_isolation", strlen("SELECT @@tx_isolation")) @@ -2731,41 +2771,9 @@ void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { } } } - if (get_status(STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT)==false) { // we search if prepared was already executed - if (!strncasecmp(query_digest_text,"PREPARE ", strlen("PREPARE "))) { - set_status(true, STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT); - } - } - if (get_status(STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE)==false) { // we search for temporary if not already set - if (!strncasecmp(query_digest_text,"CREATE TEMPORARY TABLE ", strlen("CREATE TEMPORARY TABLE "))) { - set_status(true, STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE); - } - } - if (get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES)==false) { // we search for lock tables only if not already set - if (!strncasecmp(query_digest_text,"LOCK TABLE", strlen("LOCK TABLE"))) { - set_status(true, STATUS_MYSQL_CONNECTION_LOCK_TABLES); - } - } - if (get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES)==false) { // we search for lock tables only if not already set - if (!strncasecmp(query_digest_text,"FLUSH TABLES WITH READ LOCK", strlen("FLUSH TABLES WITH READ LOCK"))) { // issue 613 - set_status(true, STATUS_MYSQL_CONNECTION_LOCK_TABLES); - } - } - if (get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES)==true) { - if (!strncasecmp(query_digest_text,"UNLOCK TABLES", strlen("UNLOCK TABLES"))) { - set_status(false, STATUS_MYSQL_CONNECTION_LOCK_TABLES); - } - } - if (get_status(STATUS_MYSQL_CONNECTION_GET_LOCK)==false) { // we search for get_lock if not already set - if (strcasestr(query_digest_text,"GET_LOCK(")) { - set_status(true, STATUS_MYSQL_CONNECTION_GET_LOCK); - } - } - if (get_status(STATUS_MYSQL_CONNECTION_FOUND_ROWS)==false) { // we search for SQL_CALC_FOUND_ROWS if not already set - if (strcasestr(query_digest_text,"SQL_CALC_FOUND_ROWS")) { - set_status(true, STATUS_MYSQL_CONNECTION_FOUND_ROWS); - } - } +} + +void MySQL_Connection::ProcessQueryAndSetStatusFlags_Savepoint(char *query_digest_text) { if (get_status(STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT)==false) { if (mysql) { if ( @@ -2795,6 +2803,9 @@ void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { set_status(false, STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT); } } +} + +void MySQL_Connection::ProcessQueryAndSetStatusFlags_SetBackslashEscapes() { if (mysql) { if (myds && myds->sess) { if (myds->sess->client_myds && myds->sess->client_myds->myconn) { @@ -2808,6 +2819,71 @@ void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { } } +void MySQL_Connection::ProcessQueryAndSetStatusFlags(char *query_digest_text) { + if (query_digest_text==NULL) return; + // unknown what to do with multiplex + int mul=-1; + if (myds) { + if (myds->sess) { + if (myds->sess->qpo) { + mul=myds->sess->qpo->multiplex; + if (mul==0) { + set_status(true, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX); + } else { + if (mul==1) { + set_status(false, STATUS_MYSQL_CONNECTION_NO_MULTIPLEX); + } + } + } + } + } + + ProcessQueryAndSetStatusFlags_Warnings(query_digest_text); + + ProcessQueryAndSetStatusFlags_UserVariables(query_digest_text, mul); + + if (get_status(STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT)==false) { // we search if prepared was already executed + if (!strncasecmp(query_digest_text,"PREPARE ", strlen("PREPARE "))) { + set_status(true, STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE)==false) { // we search for temporary if not already set + if (!strncasecmp(query_digest_text,"CREATE TEMPORARY TABLE ", strlen("CREATE TEMPORARY TABLE "))) { + set_status(true, STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES)==false) { // we search for lock tables only if not already set + if (!strncasecmp(query_digest_text,"LOCK TABLE", strlen("LOCK TABLE"))) { + set_status(true, STATUS_MYSQL_CONNECTION_LOCK_TABLES); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES)==false) { // we search for lock tables only if not already set + if (!strncasecmp(query_digest_text,"FLUSH TABLES WITH READ LOCK", strlen("FLUSH TABLES WITH READ LOCK"))) { // issue 613 + set_status(true, STATUS_MYSQL_CONNECTION_LOCK_TABLES); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES)==true) { + if (!strncasecmp(query_digest_text,"UNLOCK TABLES", strlen("UNLOCK TABLES"))) { + set_status(false, STATUS_MYSQL_CONNECTION_LOCK_TABLES); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_GET_LOCK)==false) { // we search for get_lock if not already set + if (strcasestr(query_digest_text,"GET_LOCK(")) { + set_status(true, STATUS_MYSQL_CONNECTION_GET_LOCK); + } + } + if (get_status(STATUS_MYSQL_CONNECTION_FOUND_ROWS)==false) { // we search for SQL_CALC_FOUND_ROWS if not already set + if (strcasestr(query_digest_text,"SQL_CALC_FOUND_ROWS")) { + set_status(true, STATUS_MYSQL_CONNECTION_FOUND_ROWS); + } + } + + ProcessQueryAndSetStatusFlags_Savepoint(query_digest_text); + + ProcessQueryAndSetStatusFlags_SetBackslashEscapes(); + +} + void MySQL_Connection::optimize() { if (mysql->net.max_packet > 65536) { // FIXME: temporary, maybe for very long time . This needs to become a global variable if ( ( mysql->net.buff == mysql->net.read_pos ) && ( mysql->net.read_pos == mysql->net.write_pos ) ) { @@ -2852,11 +2928,11 @@ int MySQL_Connection::async_send_simple_command(short event, char *stmt, unsigne PROXY_TRACE(); assert(mysql); assert(ret_mysql); - server_status=parent->status; // we copy it here to avoid race condition. The caller will see this + server_status=parent->get_status(); // we copy it here to avoid race condition. The caller will see this if ( - (parent->status==MYSQL_SERVER_STATUS_OFFLINE_HARD) // the server is OFFLINE as specific by the user + (parent->get_status()==MYSQL_SERVER_STATUS_OFFLINE_HARD) // the server is OFFLINE as specific by the user || - (parent->status==MYSQL_SERVER_STATUS_SHUNNED && parent->shunned_automatic==true && parent->shunned_and_kill_all_connections==true) // the server is SHUNNED due to a serious issue + (parent->get_status()==MYSQL_SERVER_STATUS_SHUNNED && parent->shunned_automatic==true && parent->shunned_and_kill_all_connections==true) // the server is SHUNNED due to a serious issue ) { return -1; } @@ -3008,3 +3084,87 @@ void MySQL_Connection::set_ssl_params(MYSQL *mysql, MySQLServers_SslParams *ssl_ ( ssl_params->ssl_crlpath.length() > 0 ? ssl_params->ssl_crlpath.c_str() : NULL ) ); } } + +void MySQL_Connection::get_mysql_info_json(json& j) { + char buff[32]; + sprintf(buff,"%p",mysql); + j["address"] = buff; + j["host"] = ( mysql->host ? mysql->host : "" ); + j["host_info"] = ( mysql->host_info ? mysql->host_info : "" ); + j["port"] = mysql->port; + j["server_version"] = ( mysql->server_version ? mysql->server_version : "" ); + j["user"] = ( mysql->user ? mysql->user : "" ); + j["unix_socket"] = (mysql->unix_socket ? mysql->unix_socket : ""); + j["db"] = (mysql->db ? mysql->db : ""); + j["affected_rows"] = mysql->affected_rows; + j["insert_id"] = mysql->insert_id; + j["thread_id"] = mysql->thread_id; + j["server_status"] = mysql->server_status; + j["charset"] = mysql->charset->nr; + j["charset_name"] = mysql->charset->csname; + j["options"]["charset_name"] = ( mysql->options.charset_name ? mysql->options.charset_name : "" ); + j["options"]["use_ssl"] = mysql->options.use_ssl; + j["net"]["last_errno"] = mysql->net.last_errno; + j["net"]["fd"] = mysql->net.fd; + j["net"]["max_packet_size"] = mysql->net.max_packet_size; + j["net"]["sqlstate"] = mysql->net.sqlstate; +} + +void MySQL_Connection::get_backend_conn_info_json(json& j) { + for (auto idx = 0; idx < SQL_NAME_LAST_LOW_WM; idx++) { + variables[idx].fill_server_internal_session(j, idx); + } + for (std::vector::const_iterator it_c = dynamic_variables_idx.begin(); it_c != dynamic_variables_idx.end(); it_c++) { + variables[*it_c].fill_server_internal_session(j, *it_c); + } + char buff[32]; + sprintf(buff,"%p", this); + j["address"] = buff; + j["auto_increment_delay_token"] = auto_increment_delay_token; + j["bytes_recv"] = bytes_info.bytes_recv; + j["bytes_sent"] = bytes_info.bytes_sent; + j["questions"] = statuses.questions; + j["myconnpoll_get"] = statuses.myconnpoll_get; + j["myconnpoll_put"] = statuses.myconnpoll_put; + j["session_track_gtids"] = ( options.session_track_gtids ? options.session_track_gtids : "") ; + j["init_connect"] = ( options.init_connect ? options.init_connect : ""); + j["init_connect_sent"] = options.init_connect_sent; + j["autocommit"] = ( options.autocommit ? "ON" : "OFF" ); + j["last_set_autocommit"] = options.last_set_autocommit; + j["no_backslash_escapes"] = options.no_backslash_escapes; + j["warning_count"] = warning_count; + json& js = j["status"]; + js["get_lock"] = get_status(STATUS_MYSQL_CONNECTION_GET_LOCK); + js["lock_tables"] = get_status(STATUS_MYSQL_CONNECTION_LOCK_TABLES); + js["has_savepoint"] = get_status(STATUS_MYSQL_CONNECTION_HAS_SAVEPOINT); + js["temporary_table"] = get_status(STATUS_MYSQL_CONNECTION_TEMPORARY_TABLE); + js["user_variable"] = get_status(STATUS_MYSQL_CONNECTION_USER_VARIABLE); + js["found_rows"] = get_status(STATUS_MYSQL_CONNECTION_FOUND_ROWS); + js["no_multiplex"] = get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX); + js["no_multiplex_HG"] = get_status(STATUS_MYSQL_CONNECTION_NO_MULTIPLEX_HG); + js["compression"] = get_status(STATUS_MYSQL_CONNECTION_COMPRESSION); + js["prepared_statement"] = get_status(STATUS_MYSQL_CONNECTION_PREPARED_STATEMENT); + js["has_warnings"] = get_status(STATUS_MYSQL_CONNECTION_HAS_WARNINGS); + { + // MultiplexDisabled : status returned by MySQL_Connection::MultiplexDisabled(); + // MultiplexDisabled_ext : status returned by MySQL_Connection::MultiplexDisabled() || MySQL_Connection::isActiveTransaction() + bool multiplex_disabled = MultiplexDisabled(); + j["MultiplexDisabled"] = multiplex_disabled; + if (multiplex_disabled == false) { + if (IsActiveTransaction() == true) { + multiplex_disabled = true; + } + } + j["MultiplexDisabled_ext"] = multiplex_disabled; + } + j["ps"]["backend_stmt_to_global_ids"] = local_stmts->backend_stmt_to_global_ids; + j["ps"]["global_stmt_to_backend_ids"] = local_stmts->global_stmt_to_backend_ids; + j["client_flag"]["value"] = options.client_flag; + j["client_flag"]["client_found_rows"] = (options.client_flag & CLIENT_FOUND_ROWS ? 1 : 0); + j["client_flag"]["client_multi_statements"] = (options.client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0); + j["client_flag"]["client_deprecate_eof"] = (options.client_flag & CLIENT_DEPRECATE_EOF ? 1 : 0); + if (mysql && ret_mysql) { + json& jcm = j["mysql"]; + get_mysql_info_json(jcm); + } +} diff --git a/lib/mysql_data_stream.cpp b/lib/mysql_data_stream.cpp index f658992d21..2407f3b7ee 100644 --- a/lib/mysql_data_stream.cpp +++ b/lib/mysql_data_stream.cpp @@ -1549,3 +1549,73 @@ bool MySQL_Data_Stream::data_in_rbio() { } return false; } + +void MySQL_Data_Stream::get_client_myds_info_json(json& j) { + json& jc1 = j["client"]; + json& jc2 = j["conn"]; + jc1["stream"]["pkts_recv"] = pkts_recv; + jc1["stream"]["pkts_sent"] = pkts_sent; + jc1["stream"]["bytes_recv"] = bytes_info.bytes_recv; + jc1["stream"]["bytes_sent"] = bytes_info.bytes_sent; + jc1["client_addr"]["address"] = ( addr.addr ? addr.addr : "" ); + jc1["client_addr"]["port"] = addr.port; + jc1["proxy_addr"]["address"] = ( proxy_addr.addr ? proxy_addr.addr : "" ); + jc1["proxy_addr"]["port"] = proxy_addr.port; + jc1["encrypted"] = encrypted; + if (encrypted) { + const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); + if (cipher) { + const char * name = SSL_CIPHER_get_name(cipher); + if (name) { + j["ssl_cipher"] = name; + } + } + } + jc1["DSS"] = DSS; + jc1["switching_auth_sent"] = switching_auth_sent; + jc1["switching_auth_type"] = switching_auth_type; + jc1["prot"]["sent_auth_plugin_id"] = myprot.sent_auth_plugin_id; + jc1["prot"]["auth_plugin_id"] = myprot.auth_plugin_id; + + switch (myprot.auth_plugin_id) { + case AUTH_MYSQL_NATIVE_PASSWORD: + jc1["prot"]["auth_plugin"] = "mysql_native_password"; + break; + case AUTH_MYSQL_CLEAR_PASSWORD: + jc1["prot"]["auth_plugin"] = "mysql_clear_password"; + break; + case AUTH_MYSQL_CACHING_SHA2_PASSWORD: + jc1["prot"]["auth_plugin"] = "caching_sha2_password"; + break; + default: + break; + } + if (myconn != NULL) { // only if myconn is defined + if (myconn->userinfo != NULL) { // only if userinfo is defined + jc1["userinfo"]["username"] = ( myconn->userinfo->username ? myconn->userinfo->username : "" ); + jc1["userinfo"]["schemaname"] = ( myconn->userinfo->schemaname ? myconn->userinfo->schemaname : "" ); +#ifdef DEBUG + jc1["userinfo"]["password"] = ( myconn->userinfo->password ? myconn->userinfo->password : "" ); +#endif + } + jc2["session_track_gtids"] = ( myconn->options.session_track_gtids ? myconn->options.session_track_gtids : "") ; + for (auto idx = 0; idx < SQL_NAME_LAST_LOW_WM; idx++) { + myconn->variables[idx].fill_client_internal_session(jc2, idx); + } + { + for (std::vector::const_iterator it_c = myconn->dynamic_variables_idx.begin(); it_c != myconn->dynamic_variables_idx.end(); it_c++) { + myconn->variables[*it_c].fill_client_internal_session(jc2, *it_c); + } + } + + jc2["autocommit"] = ( myconn->options.autocommit ? "ON" : "OFF" ); + jc2["client_flag"]["value"] = myconn->options.client_flag; + jc2["client_flag"]["client_found_rows"] = (myconn->options.client_flag & CLIENT_FOUND_ROWS ? 1 : 0); + jc2["client_flag"]["client_multi_statements"] = (myconn->options.client_flag & CLIENT_MULTI_STATEMENTS ? 1 : 0); + jc2["client_flag"]["client_multi_results"] = (myconn->options.client_flag & CLIENT_MULTI_RESULTS ? 1 : 0); + jc2["client_flag"]["client_deprecate_eof"] = (myconn->options.client_flag & CLIENT_DEPRECATE_EOF ? 1 : 0); + jc2["no_backslash_escapes"] = myconn->options.no_backslash_escapes; + jc2["status"]["compression"] = myconn->get_status(STATUS_MYSQL_CONNECTION_COMPRESSION); + jc2["ps"]["client_stmt_to_global_ids"] = myconn->local_stmts->client_stmt_to_global_ids; + } +} diff --git a/src/Makefile b/src/Makefile index 093f303be4..4a3b1dab45 100644 --- a/src/Makefile +++ b/src/Makefile @@ -149,11 +149,18 @@ ifeq ($(TEST_WITHASAN),1) WASAN += -DTEST_WITHASAN endif +NOJEMALLOC := $(shell echo $(NOJEMALLOC)) +ifeq ($(NOJEMALLOC),1) +NOJEM=-DNOJEM +else +NOJEM= +endif + MYCXXFLAGS := $(STDCPP) ifeq ($(CXX),clang++) MYCXXFLAGS += -fuse-ld=lld endif -MYCXXFLAGS += $(IDIRS) $(OPTZ) $(DEBUG) $(PSQLCH) -DGITVERSION=\"$(GIT_VERSION)\" $(WGCOV) $(WASAN) +MYCXXFLAGS += $(IDIRS) $(OPTZ) $(DEBUG) $(PSQLCH) -DGITVERSION=\"$(GIT_VERSION)\" $(NOJEM) $(WGCOV) $(WASAN) STATICMYLIBS := -Wl,-Bstatic -lconfig -lproxysql -ldaemon -lconfig++ -lre2 -lpcrecpp -lpcre -lmariadbclient -lhttpserver -lmicrohttpd -linjection -lcurl -lssl -lcrypto -lev diff --git a/src/main.cpp b/src/main.cpp index 52c959e3e0..c98d6a6cf2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -409,7 +409,7 @@ static volatile int load_; //#else //const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,purge:decay"; #ifndef __FreeBSD__ -const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,prof:true,prof_leak:true,lg_prof_sample:20,lg_prof_interval:30,prof_active:false"; +const char *malloc_conf = "xmalloc:true,lg_tcache_max:16,prof:true,prof_accum:true,prof_leak:true,lg_prof_sample:20,lg_prof_interval:30,prof_active:false"; #endif //#endif /* DEBUG */ //const char *malloc_conf = "prof_leak:true,lg_prof_sample:0,prof_final:true,xmalloc:true,lg_tcache_max:16"; @@ -1144,7 +1144,6 @@ void ProxySQL_Main_init_phase3___start_all() { GloAdmin->init_mysql_servers(); GloAdmin->init_proxysql_servers(); GloAdmin->load_scheduler_to_runtime(); - GloAdmin->proxysql_restapi().load_restapi_to_runtime(); #ifdef DEBUG std::cerr << "Main phase3 : GloAdmin initialized in "; #endif @@ -1216,6 +1215,17 @@ void ProxySQL_Main_init_phase3___start_all() { if (GloMyLdapAuth) { GloAdmin->init_ldap_variables(); } + + // HTTP Server should be initialized after other modules. See #4510 + GloAdmin->init_http_server(); + GloAdmin->proxysql_restapi().load_restapi_to_runtime(); + + // Signal ProxySQL_Admin that all modules have been started + GloAdmin->all_modules_started = true; + + // Load the config not previously loaded for these modules + GloAdmin->load_http_server(); + GloAdmin->load_restapi_server(); } @@ -1858,7 +1868,7 @@ void handleProcessRestart() { // Calculate wait time using exponential backoff int waitTime = 1 << restartAttempts; parent_open_error_log(); - proxy_info("ProxySQL exited after only %d seconds , below the %d seconds threshold. Restarting attempt %d\n", elapsed_seconds, EXECUTION_THRESHOLD, restartAttempts); + proxy_info("ProxySQL exited after only %ld seconds , below the %d seconds threshold. Restarting attempt %d\n", elapsed_seconds, EXECUTION_THRESHOLD, restartAttempts); proxy_info("Angel process is waiting %d seconds before starting a new ProxySQL process\n", waitTime); parent_close_error_log(); @@ -1901,7 +1911,61 @@ void handleProcessRestart() { } while (pid > 0); } +#ifndef NOJEM +int print_jemalloc_conf() { + int rc = 0; + + bool xmalloc = 0; + bool prof_accum = 0; + bool prof_leak = 0; + + size_t lg_cache_max = 0; + size_t lg_prof_sample = 0; + size_t lg_prof_interval = 0; + + size_t bool_sz = sizeof(bool); + size_t size_sz = sizeof(size_t); + size_t ssize_sz = sizeof(ssize_t); + + rc = mallctl("config.xmalloc", &xmalloc, &bool_sz, NULL, 0); + if (rc) { proxy_error("Failed to fetch 'config.xmalloc' with error %d", rc); return rc; } + + rc = mallctl("opt.lg_tcache_max", &lg_cache_max, &size_sz, NULL, 0); + if (rc) { proxy_error("Failed to fetch 'opt.lg_tcache_max' with error %d", rc); return rc; } + + rc = mallctl("opt.prof_accum", &prof_accum, &bool_sz, NULL, 0); + if (rc) { proxy_error("Failed to fetch 'opt.prof_accum' with error %d", rc); return rc; } + + rc = mallctl("opt.prof_leak", &prof_leak, &bool_sz, NULL, 0); + if (rc) { proxy_error("Failed to fetch 'opt.prof_leak' with error %d", rc); return rc; } + + rc = mallctl("opt.lg_prof_sample", &lg_prof_sample, &size_sz, NULL, 0); + if (rc) { proxy_error("Failed to fetch 'opt.lg_prof_sample' with error %d", rc); return rc; } + + rc = mallctl("opt.lg_prof_interval", &lg_prof_interval, &ssize_sz, NULL, 0); + if (rc) { proxy_error("Failed to fetch 'opt.lg_prof_interval' with error %d", rc); return rc; } + + proxy_info( + "Using jemalloc with MALLOC_CONF:" + " config.xmalloc:%d, lg_tcache_max:%lu, opt.prof_accum:%d, opt.prof_leak:%d," + " opt.lg_prof_sample:%lu, opt.lg_prof_interval:%lu, rc:%d\n", + xmalloc, lg_cache_max, prof_accum, prof_leak, lg_prof_sample, lg_prof_interval, rc + ); + + return 0; +} +#else +int print_jemalloc_conf() { + return 0; +} +#endif + int main(int argc, const char * argv[]) { + // Output current jemalloc conf; no action taken when disabled + { + int rc = print_jemalloc_conf(); + if (rc) { exit(EXIT_FAILURE); } + } { MYSQL *my = mysql_init(NULL); diff --git a/src/proxy_tls.cpp b/src/proxy_tls.cpp index d43f25f8c1..433bb173fc 100644 --- a/src/proxy_tls.cpp +++ b/src/proxy_tls.cpp @@ -417,8 +417,15 @@ int ProxySQL_create_or_load_TLS(bool bootstrap, std::string& msg) { // verifications purposes. if (!SSL_CTX_load_verify_locations(GloVars.global.ssl_ctx, ssl_ca_fp, ssl_ca_fp)) { proxy_error("Unable to load CA certificates location for verification. Shutting down\n"); - exit(EXIT_SUCCESS); // we exit gracefully to not be restarted } + + // Completely disable session tickets and session-cache. SSL sessions resume/tickets aren't supported + // right now, so disabling them shouldn't have negative effects. On the other hand, enabling them can + // lead to invalid SSL handshakes when the client tries to reuse a previously issued session ticket. + // In this scenario an invalid handshake will take place, and the client will be disconnected. Some + // clients (MySQL > 8.0.29) attempt session reuses during reconnect operations. + SSL_CTX_set_options(GloVars.global.ssl_ctx, SSL_OP_NO_TICKET); + SSL_CTX_set_session_cache_mode(GloVars.global.ssl_ctx, SSL_SESS_CACHE_OFF); } else { // here we use global.tmp_ssl_ctx instead of global.ssl_ctx // because we will try to swap at the end @@ -478,6 +485,9 @@ int ProxySQL_create_or_load_TLS(bool bootstrap, std::string& msg) { } if (ret == 0) { SSL_CTX_set_verify(GloVars.global.ssl_ctx, SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE, callback_ssl_verify_peer); + // Completely disable session tickets and session-cache. See comment above. + SSL_CTX_set_options(GloVars.global.ssl_ctx, SSL_OP_NO_TICKET); + SSL_CTX_set_session_cache_mode(GloVars.global.ssl_ctx, SSL_SESS_CACHE_OFF); } X509_free(x509); EVP_PKEY_free(pkey); diff --git a/test/deps/Makefile b/test/deps/Makefile index 0570da1f43..90c2bcb348 100644 --- a/test/deps/Makefile +++ b/test/deps/Makefile @@ -10,7 +10,7 @@ DEPS_PATH := $(PROXYSQL_PATH)/deps default: all .PHONY: all -all: mariadb_client mysql_client +all: mariadb_client mysql_client mysql8_client ### test deps targets @@ -25,15 +25,6 @@ mariadb-connector-c/mariadb-connector-c/libmariadb/libmariadbclient.a: mariadb_client: mariadb-connector-c/mariadb-connector-c/libmariadb/libmariadbclient.a - -#mysql-connector-c/mysql-connector-c/libmysql/libmysqlclient.a: -# cd mysql-connector-c && rm -rf mysql-connector-c-*-src/ || true -# cd mysql-connector-c && tar -zxf mysql-connector-c-*-src.tar.gz -# cd mysql-connector-c/mysql-connector-c && patch -p0 < ../CMakeLists.txt.patch -# cd mysql-connector-c/mysql-connector-c && patch -p0 < ../install_macros.cmake.patch -# cd mysql-connector-c/mysql-connector-c && cmake . -DCMAKE_BUILD_TYPE=RelWithDebInfo -DOPENSSL_ROOT_DIR=$(DEPS_PATH)/libssl/openssl -# cd mysql-connector-c/mysql-connector-c && CC=${CC} CXX=${CXX} ${MAKE} mysqlclient mysql - mysql-connector-c/mysql-boost-5.7.44.tar.gz: cd mysql-connector-c && curl -C - -O -s https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-boost-5.7.44.tar.gz || wget -nc -q https://cdn.mysql.com//Downloads/MySQL-5.7/mysql-boost-5.7.44.tar.gz @@ -42,12 +33,25 @@ mysql-connector-c/mysql-connector-c/libmysql/libmysqlclient.a: mysql-connector-c cd mysql-connector-c && tar -zxf mysql-boost-5.7.*.tar.gz cd mysql-connector-c && ln -fsT $$(ls -1d mysql-5.7.*/) mysql-connector-c cd mysql-connector-c/mysql-connector-c && cmake . -DWITH_BOOST=./boost -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O0 -ggdb -DNDEBUG -fPIC" -DOPENSSL_ROOT_DIR=$(DEPS_PATH)/libssl/openssl -# cd mysql-connector-c/mysql-connector-c && cmake . -DWITH_BOOST=./boost -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS_DEBUG="-O0 -ggdb -fPIC" -DOPENSSL_ROOT_DIR=$(DEPS_PATH)/libssl/openssl cd mysql-connector-c/mysql-connector-c && CC=${CC} CXX=${CXX} ${MAKE} mysqlclient mysql cd mysql-connector-c/mysql-connector-c && cp archive_output_directory/libmysqlclient.a libmysql/ mysql_client: mysql-connector-c/mysql-connector-c/libmysql/libmysqlclient.a +mysql-connector-c-8.4.0/mysql-8.4.0.tar.gz: + cd mysql-connector-c-8.4.0 && curl -C - -O -s https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.0.tar.gz || wget -nc -q https://cdn.mysql.com//Downloads/MySQL-8.4/mysql-8.4.0.tar.gz + +mysql-connector-c-8.4.0/mysql-connector-c/libmysql/libmysqlclient.a: mysql-connector-c-8.4.0/mysql-8.4.0.tar.gz + cd mysql-connector-c-8.4.0 && rm -rf mysql-*/ || true + cd mysql-connector-c-8.4.0 && tar -zxf mysql-*.tar.gz + cd mysql-connector-c-8.4.0 && ln -fsT $$(ls -1d mysql-8.4.*/) mysql-connector-c + cd mysql-connector-c-8.4.0/mysql-connector-c && cmake . -DFORCE_INSOURCE_BUILD=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DWITHOUT_SERVER=ON -DDOWNLOAD_BOOST=1 -DWITH_BOOST=./mysql-server/downloads/ -DWITH_UNIT_TESTS=OFF \ + -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O0 -ggdb -DNDEBUG -fPIC" -DOPENSSL_ROOT_DIR=$(DEPS_PATH)/libssl/openssl + cd mysql-connector-c-8.4.0/mysql-connector-c && CC=${CC} CXX=${CXX} ${MAKE} + cd mysql-connector-c-8.4.0/mysql-connector-c && cp archive_output_directory/libmysqlclient.a libmysql/ + +mysql8_client: mysql-connector-c-8.4.0/mysql-connector-c/libmysql/libmysqlclient.a ### clean targets @@ -56,6 +60,7 @@ mysql_client: mysql-connector-c/mysql-connector-c/libmysql/libmysqlclient.a cleanall: cd mariadb-connector-c && rm -rf mariadb-connector-c-*/ || true cd mysql-connector-c && rm -rf mysql-5.7.*/ || true + cd mysql-connector-c-8.4.0 && rm -rf mysql-8.4.*/ || true .PHONY: clean .SILENT: clean @@ -65,3 +70,6 @@ clean: cd mysql-connector-c/mysql-connector-c && $(MAKE) --no-print-directory clean || true cd mysql-connector-c/mysql-connector-c && rm -f CMakeCache.txt || true cd mysql-connector-c/mysql-connector-c && rm -f libmysql/libmysqlclient.a || true + cd mysql-connector-c-8.4.0/mysql-connector-c && $(MAKE) --no-print-directory clean || true + cd mysql-connector-c-8.4.0/mysql-connector-c && rm -f CMakeCache.txt || true + cd mysql-connector-c-8.4.0/mysql-connector-c && rm -f libmysql/libmysqlclient.a || true diff --git a/test/deps/mysql-connector-c-8.4.0/mysql-connector-c b/test/deps/mysql-connector-c-8.4.0/mysql-connector-c new file mode 120000 index 0000000000..986e2fbe69 --- /dev/null +++ b/test/deps/mysql-connector-c-8.4.0/mysql-connector-c @@ -0,0 +1 @@ +mysql-8.4.0/ \ No newline at end of file diff --git a/test/tap/Makefile b/test/tap/Makefile index ee93f10b31..66f1195a09 100644 --- a/test/tap/Makefile +++ b/test/tap/Makefile @@ -13,7 +13,7 @@ test_deps: cd ../deps && CC=${CC} CXX=${CXX} ${MAKE} .PHONY: tap -tap: +tap: test_deps cd tap && CC=${CC} CXX=${CXX} ${MAKE} .PHONY: tests @@ -25,9 +25,21 @@ tests_with_deps: tap test_deps cd tests_with_deps && CC=${CC} CXX=${CXX} ${MAKE} $(MAKECMDGOALS) +.PHONY: clean_utils +.SILENT: clean_utils +clean_utils: + cd tap && ${MAKE} -s clean_utils + .PHONY: clean .SILENT: clean clean: + cd tap && ${MAKE} -s clean + cd tests && ${MAKE} -s clean + cd tests_with_deps && ${MAKE} -s clean + +.PHONY: cleanall +.SILENT: cleanall +cleanall: cd ../deps && ${MAKE} -s clean cd tap && ${MAKE} -s clean cd tests && ${MAKE} -s clean diff --git a/test/tap/tap/Makefile b/test/tap/tap/Makefile index 831948a777..1f724aee77 100644 --- a/test/tap/tap/Makefile +++ b/test/tap/tap/Makefile @@ -20,6 +20,18 @@ MARIADB_PATH := $(DEPS_PATH)/mariadb-client-library/mariadb_client MARIADB_IDIR := $(MARIADB_PATH)/include MARIADB_LDIR := $(MARIADB_PATH)/libmariadb +TEST_DEPS_PATH := $(PROXYSQL_PATH)/test/deps + +TEST_MYSQL_PATH := $(TEST_DEPS_PATH)/mysql-connector-c/mysql-connector-c +TEST_MYSQL_IDIR := $(TEST_MYSQL_PATH)/include +TEST_MYSQL_EDIR := $(TEST_MYSQL_PATH)/libbinlogevents/export/ +TEST_MYSQL_LDIR := $(TEST_MYSQL_PATH)/libmysql + +TEST_MYSQL8_PATH := $(TEST_DEPS_PATH)/mysql-connector-c-8.4.0/mysql-connector-c +TEST_MYSQL8_IDIR := $(TEST_MYSQL8_PATH)/include +TEST_MYSQL8_EDIR := $(TEST_MYSQL8_PATH)/libbinlogevents/export/ +TEST_MYSQL8_LDIR := $(TEST_MYSQL8_PATH)/libmysql + CURL_PATH := $(DEPS_PATH)/curl/curl CURL_IDIR := $(CURL_PATH)/include CURL_LDIR := $(CURL_PATH)/lib/.libs @@ -32,10 +44,13 @@ DOTENV_PATH := ./cpp-dotenv/static/cpp-dotenv DOTENV_IDIR := $(DOTENV_PATH)/include DOTENV_LDIR := $(DOTENV_PATH) +RE2_PATH := $(DEPS_PATH)/re2/re2 +RE2_IDIR := $(RE2_PATH) +RE2_LDIR := $(RE2_PATH)/obj LIBPROXYSQLAR := $(PROXYSQL_LDIR)/libproxysql.a -IDIRS := -I$(PROXYSQL_IDIR) -I$(JSON_IDIR) -I$(MARIADB_IDIR) -I${CURL_IDIR} -I${SQLITE3_IDIR} -I$(DOTENV_IDIR) +IDIRS := -I$(PROXYSQL_IDIR) -I$(JSON_IDIR) -I${CURL_IDIR} -I${SQLITE3_IDIR} -I$(DOTENV_IDIR) -I$(RE2_IDIR) ### detect compiler support for c++11/17 CPLUSPLUS := $(shell ${CC} -std=c++17 -dM -E -x c++ /dev/null 2>/dev/null | grep -F __cplusplus | egrep -o '[0-9]{6}L') @@ -76,10 +91,11 @@ endif default: all .PHONY: all -all: libtap.a libtap.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so +all: libtap_mariadb.a libtap_mysql57.a libtap_mysql8.a \ + libtap.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so libre2.so debug: OPT := $(STDCPP) -O0 -DDEBUG -ggdb -Wl,--no-as-needed $(WASAN) -debug: libtap.a libtap.so +debug: libtap_mariadb.a libtap_mysql57.a libtap_mysql8.a libtap.so ### helper targets @@ -87,17 +103,29 @@ debug: libtap.a libtap.so command_line.o: command_line.cpp cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a libcurl.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so $(CXX) -fPIC -c command_line.cpp $(IDIRS) $(OPT) -utils.o: utils.cpp cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a libcurl.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so - $(CXX) -fPIC -c utils.cpp $(IDIRS) $(OPT) +utils_mariadb.o: utils.cpp cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a libcurl.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so + $(CXX) -fPIC -c utils.cpp $(IDIRS) -I$(MARIADB_IDIR) $(OPT) -o $@ + +utils_mysql57.o: utils.cpp cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a libcurl.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so + $(CXX) -DDISABLE_WARNING_COUNT_LOGGING -fPIC -c utils.cpp $(IDIRS) -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) $(OPT) -o $@ + +utils_mysql8.o: utils.cpp cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a libcurl.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so + $(CXX) -DDISABLE_WARNING_COUNT_LOGGING -fPIC -c utils.cpp $(IDIRS) -I$(TEST_MYSQL8_IDIR) -I$(TEST_MYSQL_EDIR) $(OPT) -o $@ tap.o: tap.cpp cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a libcurl.so libssl.so.3 libcrypto.so.3 libcpp_dotenv.so $(CXX) -fPIC -c tap.cpp $(IDIRS) $(OPT) -libtap.a: tap.o command_line.o utils.o cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a - ar rcs libtap.a tap.o command_line.o utils.o $(SQLITE3_LDIR)/sqlite3.o $(PROXYSQL_LDIR)/obj/sha256crypt.oo +libtap_mariadb.a: tap.o command_line.o utils_mariadb.o cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a + ar rcs libtap_mariadb.a tap.o command_line.o utils_mariadb.o $(SQLITE3_LDIR)/sqlite3.o $(PROXYSQL_LDIR)/obj/sha256crypt.oo -libtap.so: libtap.a cpp-dotenv/dynamic/cpp-dotenv/libcpp_dotenv.so - $(CXX) -shared -o libtap.so -Wl,--whole-archive libtap.a -Wl,--no-whole-archive $(LWGCOV) +libtap_mysql57.a: tap.o command_line.o utils_mysql57.o cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a + ar rcs libtap_mysql57.a tap.o command_line.o utils_mysql57.o $(SQLITE3_LDIR)/sqlite3.o $(PROXYSQL_LDIR)/obj/sha256crypt.oo + +libtap_mysql8.a: tap.o command_line.o utils_mysql8.o cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a + ar rcs libtap_mysql8.a tap.o command_line.o utils_mysql8.o $(SQLITE3_LDIR)/sqlite3.o $(PROXYSQL_LDIR)/obj/sha256crypt.oo + +libtap.so: libtap_mariadb.a cpp-dotenv/dynamic/cpp-dotenv/libcpp_dotenv.so libre2.so + $(CXX) -shared -o libtap.so -Wl,--whole-archive libtap_mariadb.a -Wl,--no-whole-archive $(LWGCOV) ### tap deps targets @@ -114,6 +142,9 @@ libcpp_dotenv.so: cpp-dotenv/dynamic/cpp-dotenv/libcpp_dotenv.so libcurl.so: $(DEPS_PATH)/curl/curl/lib/.libs/libcurl.so cp -a $(DEPS_PATH)/curl/curl/lib/.libs/libcurl.so* . +libre2.so: $(DEPS_PATH)/re2/re2/obj/so/libre2.so + cp -a $(DEPS_PATH)/re2/re2/obj/so/libre2.so* . + cpp-dotenv/static/cpp-dotenv/libcpp_dotenv.a: cd cpp-dotenv/static && rm -rf cpp-dotenv-*/ || true cd cpp-dotenv/static && tar -zxf ../cpp-dotenv-*.tar.gz @@ -135,6 +166,13 @@ cpp-dotenv/dynamic/cpp-dotenv/libcpp_dotenv.so: ### clean targets +.SILENT: clean_utils +.PHONY: clean_utils +clean_utils: + find . -name 'utils_*.*' -delete || true + find . -name 'libtap_*.*' -delete || true + find . -name 'libtap.so' -delete || true + .SILENT: clean .PHONY: clean clean: diff --git a/test/tap/tap/command_line.cpp b/test/tap/tap/command_line.cpp index 13d6827099..ce473c77e7 100644 --- a/test/tap/tap/command_line.cpp +++ b/test/tap/tap/command_line.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include diff --git a/test/tap/tap/tap.cpp b/test/tap/tap/tap.cpp index 8afb7939cd..b067432a2d 100644 --- a/test/tap/tap/tap.cpp +++ b/test/tap/tap/tap.cpp @@ -415,6 +415,10 @@ int tests_failed() { return g_test.failed; } +int tests_last() { + return g_test.last; +} + /** @mainpage Testing C and C++ using MyTAP diff --git a/test/tap/tap/tap.h b/test/tap/tap/tap.h index 4b7e76354a..7678c1a390 100644 --- a/test/tap/tap/tap.h +++ b/test/tap/tap/tap.h @@ -311,6 +311,12 @@ void todo_end(); int tests_failed(); +/** + * @brief Return the number of tests that have passed. + */ + +int tests_last(); + /** @} */ #ifdef __cplusplus diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp index 60fda98e4f..d55e3521c0 100644 --- a/test/tap/tap/utils.cpp +++ b/test/tap/tap/utils.cpp @@ -1,30 +1,28 @@ #include #include -#include #include #include #include #include -#include #include -#include +#include +#include #include - -#include "mysql.h" - -#include "tap.h" -#include "utils.h" - #include #include -#include -#include -#include + +#include "json.hpp" +#include "re2/re2.h" #include "proxysql_utils.h" +#include "mysql.h" +#include "utils.h" +#include "tap.h" + using std::pair; using std::map; +using std::fstream; using std::string; using std::vector; @@ -38,9 +36,10 @@ using nlohmann::json; #define STMT_VECTOR_PTR(mysql) (static_cast*>(mysql->unused_3)) #define STMT_EXECUTED_VECTOR_PTR(mysql) (static_cast>*>(mysql->unused_4)) -#define STMT_FIND_INDEX(stmt,idx) const std::vector& vec_stmt = STMT_VECTOR(stmt); \ - for (size_t i = 0; i < vec_stmt.size(); i++) {\ - if (vec_stmt[i] == stmt) {\ +#define STMT_FIND_INDEX(stmt,idx) const std::vector* vec_stmt = STMT_VECTOR_PTR(stmt->mysql);\ + size_t vec_size = vec_stmt ? vec_stmt->size() : 0;\ + for (size_t i = 0; i < vec_size; i++) {\ + if ((*vec_stmt)[i] == stmt) {\ idx = i; \ break; \ }\ @@ -72,6 +71,10 @@ using nlohmann::json; vec_query.pop_back();\ } +#ifndef DISABLE_WARNING_COUNT_LOGGING + +extern "C" { + MYSQL* mysql_init_override(MYSQL* mysql, const char* file, int line) { static bool init = false; MYSQL* result = (*real_mysql_init)(mysql); @@ -88,7 +91,9 @@ MYSQL* mysql_init_override(MYSQL* mysql, const char* file, int line) { int mysql_query_override(MYSQL* mysql, const char* query, const char* file, int line) { const int result = (*real_mysql_query)(mysql, query); if (result == 0) { - LAST_QUERY_EXECUTED_STR(mysql) = query; + if (LAST_QUERY_EXECUTED_PTR(mysql)) { + LAST_QUERY_EXECUTED_STR(mysql) = query; + } if (mysql_errno(mysql) == 0 && mysql_field_count(mysql) == 0 && mysql_warning_count(mysql) > 0) { fprintf(stdout, "File %s, Line %d, [mysql_query] A warning was generated during the execution of the query:'%s', warning count:%d\n", file, line, query, mysql_warning_count(mysql)); @@ -99,7 +104,7 @@ int mysql_query_override(MYSQL* mysql, const char* query, const char* file, int MYSQL_RES* mysql_store_result_override(MYSQL* mysql, const char* file, int line) { MYSQL_RES* result = (*real_mysql_store_result)(mysql); - if (mysql_errno(mysql) == 0 && mysql_warning_count(mysql) > 0) { + if (mysql_errno(mysql) == 0 && mysql_warning_count(mysql) > 0 && LAST_QUERY_EXECUTED_PTR(mysql)) { fprintf(stdout, "File %s, Line %d, [mysql_store_result] A warning was generated during the execution of the query:'%s', warning count:%d\n", file, line, LAST_QUERY_EXECUTED_STR(mysql).c_str(), mysql_warning_count(mysql)); } @@ -107,7 +112,9 @@ MYSQL_RES* mysql_store_result_override(MYSQL* mysql, const char* file, int line) } void mysql_close_override(MYSQL* mysql, const char* file, int line) { - delete LAST_QUERY_EXECUTED_PTR(mysql); + if (LAST_QUERY_EXECUTED_PTR(mysql)) { + delete LAST_QUERY_EXECUTED_PTR(mysql); + } if (STMT_VECTOR_PTR(mysql)) { delete STMT_VECTOR_PTR(mysql); delete STMT_EXECUTED_VECTOR_PTR(mysql); @@ -176,6 +183,10 @@ my_bool mysql_stmt_close_override(MYSQL_STMT* stmt, const char* file, int line) return (*real_mysql_stmt_close)(stmt); } +} + +#endif + std::size_t count_matches(const string& str, const string& substr) { std::size_t result = 0; std::size_t pos = 0; @@ -522,6 +533,39 @@ ext_val_t ext_single_row_val(const mysql_res_row& row, const string& def } } +ext_val_t ext_single_row_val(const mysql_res_row& row, const int32_t& def_val) { + if (row.empty() || row.front().empty()) { + return { -1, def_val, {} }; + } else { + errno = 0; + char* p_end {}; + const int32_t val = std::strtol(row.front().c_str(), &p_end, 10); + + if (row[0] == p_end || errno == ERANGE) { + return { -2, def_val, string { row[0] } }; + } else { + return { EXIT_SUCCESS, val, string { row[0] } }; + } + } +} + +ext_val_t ext_single_row_val(const mysql_res_row& row, const uint32_t& def_val) { + if (row.empty() || row.front().empty()) { + return { -1, def_val, {} }; + } else { + errno = 0; + char* p_end {}; + const uint32_t val = std::strtoul(row.front().c_str(), &p_end, 10); + + if (row[0] == p_end || errno == ERANGE) { + return { -2, def_val, string { row[0] } }; + } else { + return { EXIT_SUCCESS, val, string { row[0] } }; + } + } +} + + ext_val_t ext_single_row_val(const mysql_res_row& row, const int64_t& def_val) { if (row.empty() || row.front().empty()) { return { -1, def_val, {} }; @@ -544,7 +588,7 @@ ext_val_t ext_single_row_val(const mysql_res_row& row, const uint64_t& } else { errno = 0; char* p_end {}; - const uint64_t val = std::strtoll(row.front().c_str(), &p_end, 10); + const uint64_t val = std::strtoull(row.front().c_str(), &p_end, 10); if (row[0] == p_end || errno == ERANGE) { return { -2, def_val, string { row[0] } }; @@ -1022,6 +1066,27 @@ int get_variable_value( return res; } +vector> get_all_bin_vec(size_t tg_size) { + vector> all_bin_strs {}; + vector bin_vec(tg_size, 0); + + for (size_t i = 0; i < tg_size; i++) { + if (i == 0) { + bin_vec[i] = 0; + for (const vector p : get_permutations(bin_vec)) { + all_bin_strs.push_back(p); + } + } + + bin_vec[i] = 1; + for (const vector p : get_permutations(bin_vec)) { + all_bin_strs.push_back(p); + } + } + + return all_bin_strs; +} + string to_string(const conn_cnf_t& cnf) { return string { string { "{" } @@ -1314,24 +1379,27 @@ int open_file_and_seek_end(const string& f_path, std::fstream& f_stream) { return EXIT_SUCCESS; } -std::vector get_matching_lines(std::fstream& f_stream, const std::string& regex) { - std::vector found_matches {}; +vector get_matching_lines(fstream& f_stream, const string& s_regex, bool get_matches) { + vector found_matches {}; - std::string next_line {}; - std::fstream::pos_type init_pos { f_stream.tellg() }; + string next_line {}; + fstream::pos_type init_pos { f_stream.tellg() }; - while (std::getline(f_stream, next_line)) { - std::regex regex_err_line { regex }; - std::smatch match_results {}; + while (getline(f_stream, next_line)) { + re2::RE2 regex { s_regex }; + re2::StringPiece match; - if (std::regex_search(next_line, match_results, regex_err_line)) { - found_matches.push_back({ f_stream.tellg(), next_line, match_results }); + if (get_matches && RE2::PartialMatch(next_line, regex, &match)) { + found_matches.push_back({ f_stream.tellg(), next_line, match.ToString() }); + } + if (!get_matches && RE2::PartialMatch(next_line, regex)) { + found_matches.push_back({ f_stream.tellg(), next_line, match.ToString() }); } } if (found_matches.empty() == false) { - const std::string& last_match { std::get(found_matches.back()) }; - const std::fstream::pos_type last_match_pos { std::get(found_matches.back()) }; + const string& last_match { std::get(found_matches.back()) }; + const fstream::pos_type last_match_pos { std::get(found_matches.back()) }; f_stream.clear(f_stream.rdstate() & ~std::ios_base::failbit); f_stream.seekg(last_match_pos); diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h index 7422abe146..0b918b0d06 100644 --- a/test/tap/tap/utils.h +++ b/test/tap/tap/utils.h @@ -4,19 +4,21 @@ #include #include #include -#include #include -#include -#include #include "curl/curl.h" -#include + #include "sqlite3db.h" +#include "json_fwd.hpp" #include "command_line.h" -#include "json.hpp" +#include "mysql.h" +// Improve dependency failure compilation error #ifndef DISABLE_WARNING_COUNT_LOGGING + +extern "C" { + /* We are overriding some of the mariadb APIs to extract the warning count and print it in the log. This override will apply to all TAP tests, except when the TAP test is linked with the MySQL client library (LIBMYSQL_HELPER defined). */ @@ -49,6 +51,9 @@ my_bool mysql_stmt_close_override(MYSQL_STMT* stmt, const char* file, int line); #define mysql_stmt_execute(stmt) mysql_stmt_execute_override(stmt,__FILE__,__LINE__) #define mysql_stmt_store_result(stmt) mysql_stmt_store_result_override(stmt,__FILE__,__LINE__) #define mysql_stmt_close(stmt) mysql_stmt_close_override(stmt,__FILE__,__LINE__) + +} + #endif inline std::string get_formatted_time() { @@ -92,20 +97,12 @@ int mysql_query_t(MYSQL* mysql, const char* query); } \ } while(0) -#ifdef __cplusplus -extern "C" { -#endif - int show_variable(MYSQL *mysql, const std::string& var_name, std::string& var_value); int show_admin_global_variable(MYSQL *mysql, const std::string& var_name, std::string& var_value); int set_admin_global_variable(MYSQL *mysql, const std::string& var_name, const std::string& var_value); int get_server_version(MYSQL *mysql, std::string& version); int select_config_file(MYSQL* mysql, std::string& resultset); -#ifdef __cplusplus -} -#endif - /* * @return int Zero in case of success, or the errno returned by `execvp` in case of failure. */ @@ -120,8 +117,6 @@ int execvp(const std::string& file, const std::vector& argv, std::s */ int exec(const std::string& cmd, std::string& result); - - // create table test.sbtest1 with num_rows rows int create_table_test_sbtest1(int num_rows, MYSQL *mysql); int create_table_test_sqlite_sbtest1(int num_rows, MYSQL *mysql); // as above, but for SQLite3 server @@ -171,6 +166,8 @@ struct ext_val_t { * @return An `ext_val_t` where T is the type of the provided default value. */ ext_val_t ext_single_row_val(const mysql_res_row& row, const std::string& def_val); +ext_val_t ext_single_row_val(const mysql_res_row& row, const int32_t& def_val); +ext_val_t ext_single_row_val(const mysql_res_row& row, const uint32_t& def_val); ext_val_t ext_single_row_val(const mysql_res_row& row, const int64_t& def_val); ext_val_t ext_single_row_val(const mysql_res_row& row, const uint64_t& def_val); @@ -429,6 +426,13 @@ std::vector> get_permutations(const std::vector& elem_set) { return result; } +/** + * @brief Generates permutations of binary vectors of the specified size. + * @param tg_size The target size of the binary vectors. + * @return The generated permutations. + */ +std::vector> get_all_bin_vec(size_t tg_size); + /** * @brief Struct holding options on how to performs connections for 'EOF' tests. */ @@ -585,21 +589,26 @@ std::string get_env(const std::string& var); /** * @brief Opens the file in the supplied path in the provided stream, and seeks the end of it. * @param f_path Path to the file to open. - * @param f_logfile Output parameter with the stream to be updated with the oppened file. - * @return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise. Error casuse is logged. + * @param f_logfile Output parameter with the stream to be updated with the opened file. + * @return EXIT_SUCCESS in case of success, EXIT_FAILURE otherwise. Error cause is logged. */ int open_file_and_seek_end(const std::string& f_path, std::fstream& f_stream); -using line_match_t = std::tuple; -enum LINE_MATCH_T { POS, LINE, MATCHES }; +using line_match_t = std::tuple; +enum LINE_MATCH_T { POS, LINE, MATCH }; /** * @brief Extracts the lines matching the regex from the supplied stream till reaching EOF. * @param f_stream The stream to be matched with the regex. * @param regex The regex used to match the stream line-by-line. + * @param get_matches If matched patterns should be returned (LINE_MATCH_T::MATCH). If supplied, the regex + * should contain at least one sub-pattern, or be a complete sub-pattern, otherwise it will fail to match. + * For example, regex '\d+' should become '(\d+)'. * @return All the lines found matching the regex. */ -std::vector get_matching_lines(std::fstream& f_stream, const std::string& regex); +std::vector get_matching_lines( + std::fstream& f_stream, const std::string& regex, bool get_matches=false +); /** * @brief Opens a sqlite3 db file located in the supplied path with the provided flags. diff --git a/test/tap/tests/Makefile b/test/tap/tests/Makefile index fca9b31374..b404d1ad3e 100644 --- a/test/tap/tests/Makefile +++ b/test/tap/tests/Makefile @@ -84,7 +84,6 @@ TAP_LDIR := ../tap DOTENV_DYN_PATH := $(TAP_LDIR)/cpp-dotenv/dynamic/cpp-dotenv DOTENV_DYN_IDIR := $(DOTENV_DYN_PATH)/include -#DOTENV_DYN_LDIR := $(DOTENV_DYN_PATH) DOTENV_DYN_LDIR := $(TAP_LDIR) TEST_DEPS_PATH := $(PROXYSQL_PATH)/test/deps @@ -98,6 +97,11 @@ TEST_MYSQL_IDIR := $(TEST_MYSQL_PATH)/include TEST_MYSQL_EDIR := $(TEST_MYSQL_PATH)/libbinlogevents/export/ TEST_MYSQL_LDIR := $(TEST_MYSQL_PATH)/libmysql +TEST_MYSQL8_PATH := $(TEST_DEPS_PATH)/mysql-connector-c-8.4.0/mysql-connector-c +TEST_MYSQL8_IDIR := $(TEST_MYSQL8_PATH)/include +TEST_MYSQL8_EDIR := $(TEST_MYSQL8_PATH)/libbinlogevents/export/ +TEST_MYSQL8_LDIR := $(TEST_MYSQL8_PATH)/libmysql + LIBPROXYSQLAR := $(PROXYSQL_LDIR)/libproxysql.a ODIR := $(PROXYSQL_PATH)/obj @@ -106,8 +110,14 @@ EXECUTABLE := proxysql OBJ := $(PROXYSQL_PATH)/src/obj/proxysql_global.o $(PROXYSQL_PATH)/src/obj/main.o $(PROXYSQL_PATH)/src/obj/proxy_tls.o -IDIRS := -I$(TAP_IDIR) -I$(RE2_IDIR) -I$(PROXYSQL_IDIR) -I$(JEMALLOC_IDIR) -I$(LIBCONFIG_IDIR) -I$(MARIADB_IDIR) -I$(DAEMONPATH_IDIR) -I$(MICROHTTPD_IDIR) -I$(LIBHTTPSERVER_IDIR) -I$(CURL_IDIR) -I$(EV_IDIR) -I$(PROMETHEUS_IDIR) -I$(DOTENV_DYN_IDIR) -I$(SSL_IDIR) -I$(SQLITE3_IDIR) -I$(JSON_IDIR) -LDIRS := -L$(TAP_LDIR) -L$(RE2_LDIR) -L$(PROXYSQL_LDIR) -L$(JEMALLOC_LDIR) -L$(LIBCONFIG_LDIR) -L$(MARIADB_LDIR) -L$(DAEMONPATH_LDIR) -L$(MICROHTTPD_LDIR) -L$(LIBHTTPSERVER_LDIR) -L$(CURL_LDIR) -L$(EV_LDIR) -L$(PROMETHEUS_LDIR) -L$(DOTENV_DYN_LDIR) -L$(SSL_LDIR) -L$(PCRE_LDIR) -L$(LIBINJECTION_LDIR) +SOURCES := ../tap/utils.cpp + +IDIRS := -I$(TAP_IDIR) -I$(RE2_IDIR) -I$(PROXYSQL_IDIR) -I$(JEMALLOC_IDIR) -I$(LIBCONFIG_IDIR) -I$(MARIADB_IDIR)\ + -I$(DAEMONPATH_IDIR) -I$(MICROHTTPD_IDIR) -I$(LIBHTTPSERVER_IDIR) -I$(CURL_IDIR) -I$(EV_IDIR)\ + -I$(PROMETHEUS_IDIR) -I$(DOTENV_DYN_IDIR) -I$(SSL_IDIR) -I$(SQLITE3_IDIR) -I$(JSON_IDIR) +LDIRS := -L$(TAP_LDIR) -L$(RE2_LDIR) -L$(PROXYSQL_LDIR) -L$(JEMALLOC_LDIR) -L$(LIBCONFIG_LDIR) -L$(MARIADB_LDIR)\ + -L$(DAEMONPATH_LDIR) -L$(MICROHTTPD_LDIR) -L$(LIBHTTPSERVER_LDIR) -L$(CURL_LDIR) -L$(EV_LDIR)\ + -L$(PROMETHEUS_LDIR) -L$(DOTENV_DYN_LDIR) -L$(SSL_LDIR) -L$(PCRE_LDIR) -L$(LIBINJECTION_LDIR) UNAME_S := $(shell uname -s) @@ -116,13 +126,13 @@ ifeq ($(UNAME_S),Linux) endif MYLIBS := -Wl,--export-dynamic -MYLIBS += -Wl,-Bdynamic -lgnutls -ltap -lcpp_dotenv -lcurl -lssl -lcrypto -MYLIBS += -Wl,-Bstatic -lconfig -lproxysql -ldaemon -lconfig++ -lre2 -lpcrecpp -lpcre -lmariadbclient -lhttpserver -lmicrohttpd -linjection -lev -lprometheus-cpp-pull -lprometheus-cpp-core -luuid +MYLIBS += -Wl,-Bdynamic -lgnutls -ltap -lcpp_dotenv -lcurl -lssl -lcrypto -lre2 -luuid +MYLIBS += -Wl,-Bstatic -lconfig -lproxysql -ldaemon -lconfig++ -lpcrecpp -lpcre -lmariadbclient -lhttpserver\ + -lmicrohttpd -linjection -lev -lprometheus-cpp-pull -lprometheus-cpp-core MYLIBS += -Wl,-Bdynamic -lpthread -lm -lz -lrt -ldl $(EXTRALINK) -#MYLIBS := -Wl,--export-dynamic -Wl,-Bdynamic -lssl -lcrypto -lgnutls -ltap -lcpp_dotenv -Wl,-Bstatic -lconfig -lproxysql -ldaemon -lconfig++ -lre2 -lpcrecpp -lpcre -lmariadbclient -lhttpserver -lmicrohttpd -linjection -lev -lprometheus-cpp-pull -lprometheus-cpp-core -luuid -Wl,-Bdynamic -lpthread -lm -lz -lrt -ldl $(EXTRALINK) + MYLIBSJEMALLOC := -Wl,-Bstatic -ljemalloc STATIC_LIBS := $(CITYHASH_LDIR)/libcityhash.a -#STATIC_LIBS := $(SSL_LDIR)/libssl.a $(SSL_LDIR)/libcrypto.a $(CITYHASH_LDIR)/libcityhash.a LIBCOREDUMPERAR := ifeq ($(UNAME_S),Linux) @@ -158,20 +168,23 @@ endif OPT := $(STDCPP) -O2 -ggdb -Wl,--no-as-needed -Wl,-rpath,$(TAP_LDIR) $(WGCOV) $(WASAN) -DGITVERSION=\"$(GIT_VERSION)\" - ### main targets .DEFAULT: default .PHONY: default default: all +CUSTOMARGS := -I$(TAP_IDIR) -I$(CURL_IDIR) -I$(SQLITE3_IDIR) -I$(PROXYSQL_IDIR) -I$(JSON_IDIR) -I$(SSL_IDIR) -I$(RE2_IDIR) +CUSTOMARGS += -L$(TAP_LDIR) -L$(CURL_LDIR) -L$(SSL_LDIR) -L$(RE2_LDIR) +CUSTOMARGS += -Wl,-Bdynamic -lcpp_dotenv -lcurl -lssl -lcrypto -lre2 -lpthread -lz -ldl + .PHONY: all all: tests debug: OPT := $(STDCPP) -O0 -DDEBUG -ggdb -Wl,--no-as-needed -Wl,-rpath,$(TAP_LDIR) $(WGCOV) $(WASAN) -DGITVERSION=\"$(GIT_VERSION)\" debug: tests -#tests: build_test_deps +tests: CUSTOMARGS += $(OPT) tests: tests-cpp \ tests-php \ tests-py \ @@ -179,6 +192,8 @@ tests: tests-cpp \ setparser_test \ reg_test_3504-change_user_libmariadb_helper \ reg_test_3504-change_user_libmysql_helper \ + mysql_reconnect_libmariadb-t \ + mysql_reconnect_libmysql-t \ setparser_test2 setparser_test2-t \ setparser_test3 setparser_test3-t \ set_testing-240.csv \ @@ -230,7 +245,6 @@ sh-%: %-t: %-t.cpp $(TAP_LDIR)/libtap.so $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(STATIC_LIBS) -o $@ -# $(CXX) $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) -lpthread -ldl $(STATIC_LIBS) $(TAP_LDIR)/libtap.so -o $@ galera_1_timeout_count: galera_1_timeout_count.cpp $(TAP_LDIR)/libtap.so $(CXX) -DTEST_GALERA $< ../tap/SQLite3_Server.cpp $(IDIRS) $(LDIRS) $(OPT) $(OBJ) $(MYLIBSJEMALLOC) $(MYLIBS) $(STATIC_LIBS) -o $@ @@ -268,42 +282,41 @@ setparser_test: setparser_test.cpp $(TAP_LDIR)/libtap.so $(RE2_PATH)/util/test.c setparser_test2-t: setparser_test2 ln -fs setparser_test2 setparser_test2-t -setparser_test2: setparser_test2.cpp $(TAP_LDIR)/libtap.so $(PROXYSQL_LDIR)/set_parser.cpp setparser_test_common.h $(LIBCOREDUMPERAR) +setparser_test2: setparser_test2.cpp $(TAP_LDIR)/libtap.so $(PROXYSQL_LDIR)/set_parser.cpp setparser_test_common.h $(LIBPROXYSQLAR) $(LIBCOREDUMPERAR) $(CXX) $< $(PROXYSQL_LDIR)/set_parser.cpp $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(LIBCOREDUMPERAR) -o $@ setparser_test3-t: setparser_test3 ln -fs setparser_test3 setparser_test3-t -setparser_test3: setparser_test3.cpp $(TAP_LDIR)/libtap.so $(PROXYSQL_LDIR)/set_parser.cpp setparser_test_common.h $(LIBCOREDUMPERAR) +setparser_test3: setparser_test3.cpp $(TAP_LDIR)/libtap.so $(PROXYSQL_LDIR)/set_parser.cpp setparser_test_common.h $(LIBPROXYSQLAR) $(LIBCOREDUMPERAR) $(CXX) -DPARSERDEBUG $< $(PROXYSQL_LDIR)/set_parser.cpp $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(LIBCOREDUMPERAR) -o $@ - -CUSTOMARGS := -I$(TAP_IDIR) -I$(CURL_IDIR) -I$(SQLITE3_IDIR) -I$(PROXYSQL_IDIR) -I$(JSON_IDIR) -I$(SSL_IDIR) -CUSTOMARGS += -L$(TAP_LDIR) -L$(CURL_LDIR) -L$(SSL_LDIR) -#CUSTOMARGS += -Wl,-Bstatic -lcurl -CUSTOMARGS += -Wl,-Bdynamic -ltap -lcpp_dotenv -lcurl -lssl -lcrypto -lpthread -lz -ldl -CUSTOMARGS += $(OPT) - reg_test_3504-change_user_libmariadb_helper: reg_test_3504-change_user_helper.cpp $(TAP_LDIR)/libtap.so $(CXX) -DDISABLE_WARNING_COUNT_LOGGING $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(STATIC_LIBS) -o $@ reg_test_3504-change_user_libmysql_helper: reg_test_3504-change_user_helper.cpp $(TAP_LDIR)/libtap.so - $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient $(CUSTOMARGS) -o $@ + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -Wl,-Bstatic -lmysqlclient -ltap_mysql57 $(CUSTOMARGS) -o $@ + +mysql_reconnect_libmariadb-t: mysql_reconnect.cpp $(TAP_LDIR)/libtap.so + $(CXX) -DDISABLE_WARNING_COUNT_LOGGING $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(STATIC_LIBS) -o $@ + +mysql_reconnect_libmysql-t: mysql_reconnect.cpp $(TAP_LDIR)/libtap_mysql8.a + $(CXX) -DLIBMYSQL_HELPER8 -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL8_IDIR) -I$(TEST_MYSQL8_EDIR) -L$(TEST_MYSQL8_LDIR) -lmysqlclient -ltap_mysql8 -lresolv $(CUSTOMARGS) -o $@ test_clickhouse_server_libmysql-t: test_clickhouse_server-t.cpp $(TAP_LDIR)/libtap.so - $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient $(CUSTOMARGS) -o $@ + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient -ltap_mysql57 $(CUSTOMARGS) -o $@ reg_test_stmt_resultset_err_no_rows_libmysql-t: reg_test_stmt_resultset_err_no_rows-t.cpp $(TAP_LDIR)/libtap.so - $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient $(CUSTOMARGS) -o $@ + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient -ltap_mysql57 $(CUSTOMARGS) -o $@ reg_test_mariadb_stmt_store_result_libmysql-t: reg_test_mariadb_stmt_store_result-t.cpp $(TAP_LDIR)/libtap.so - $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient $(CUSTOMARGS) -o $@ + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient -ltap_mysql57 $(CUSTOMARGS) -o $@ reg_test_mariadb_stmt_store_result_async-t: reg_test_mariadb_stmt_store_result-t.cpp $(TAP_LDIR)/libtap.so $(CXX) -DASYNC_API $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(STATIC_LIBS) -o $@ prepare_statement_err3024_libmysql-t: prepare_statement_err3024-t.cpp $(TAP_LDIR)/libtap.so - $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient $(CUSTOMARGS) -o $@ + $(CXX) -DLIBMYSQL_HELPER -DDISABLE_WARNING_COUNT_LOGGING $< -I$(TEST_MYSQL_IDIR) -I$(TEST_MYSQL_EDIR) -L$(TEST_MYSQL_LDIR) -lmysqlclient -ltap_mysql57 $(CUSTOMARGS) -o $@ prepare_statement_err3024_async-t: prepare_statement_err3024-t.cpp $(TAP_LDIR)/libtap.so $(CXX) -DASYNC_API $< $(IDIRS) $(LDIRS) $(OPT) $(MYLIBS) $(STATIC_LIBS) -o $@ diff --git a/test/tap/tests/kill_connection3-t.cpp b/test/tap/tests/kill_connection3-t.cpp index ef28ac6cb6..1c023ffb5a 100644 --- a/test/tap/tests/kill_connection3-t.cpp +++ b/test/tap/tests/kill_connection3-t.cpp @@ -191,7 +191,7 @@ int main(int argc, char** argv) { rc = run_q(proxysql_admin, s.c_str()); ok(rc == 0 , "%s" , s.c_str()); } - sleep(1); + sleep(3); for (int i = 0; i < NUM_CONNS ; i++) { MYSQL * mysql = conns[i]; int rc = run_q(mysql, "DO 1"); diff --git a/test/tap/tests/multiple_prepared_statements-t.cpp b/test/tap/tests/multiple_prepared_statements-t.cpp index ea3bed6035..d88fad720b 100644 --- a/test/tap/tests/multiple_prepared_statements-t.cpp +++ b/test/tap/tests/multiple_prepared_statements-t.cpp @@ -42,9 +42,12 @@ inline unsigned long long monotonic_time() { return (((unsigned long long) ts.tv_sec) * 1000000) + (ts.tv_nsec / 1000); } +#define NTHREADS 5 #define NCONNS 6 #define NPREP 15000 #define PROGRESS 2000 + +pthread_mutex_t mtx[NCONNS]; MYSQL* conns[NCONNS]; int ids[NCONNS*NPREP]; MYSQL_STMT * stmts[NCONNS*NPREP]; @@ -125,6 +128,163 @@ int execute_stmt(int idx) { return 0; } +void * prepare_thread(void *arg) { + int thread_id = *(int *)arg; + for (int i=0; i #include #include #include diff --git a/test/tap/tests/mysql-last_insert_id-t.cpp b/test/tap/tests/mysql-last_insert_id-t.cpp index 1e8e9d18b1..90549afa4c 100644 --- a/test/tap/tests/mysql-last_insert_id-t.cpp +++ b/test/tap/tests/mysql-last_insert_id-t.cpp @@ -18,8 +18,9 @@ inline unsigned long long monotonic_time() { } -std::string queries[4] = { +std::string queries[5] = { "SELECT LAST_INSERT_ID() LIMIT 1", + "SELECT LAST_INSERT_ID() FROM DUAL", "SELECT LAST_INSERT_ID()", "SELECT @@IDENTITY LIMIT 1", "SELECT @@IDENTITY" diff --git a/test/tap/tests/mysql_reconnect.cpp b/test/tap/tests/mysql_reconnect.cpp new file mode 100644 index 0000000000..8962d009d7 --- /dev/null +++ b/test/tap/tests/mysql_reconnect.cpp @@ -0,0 +1,200 @@ +/** + * @file mysql_reconnect.cpp + * @brief Check that reconnect works against ProxySQL with/without SSL enabled. + * @details The test requires to be compiled against libmariadb and libmysql. This allows to perform a + * regression test against libmysql regarding reconnect and SSL session tickets. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef LIBMYSQL_HELPER8 +#include +#else +#include "mysql.h" +#endif + +#include "utils.h" +#include "tap.h" +#include "command_line.h" + +using std::string; +using std::vector; + +struct _conn_cnf_t { + bool ssl; + bool eof; +}; + +int test_reconnect(const CommandLine& cl, const _conn_cnf_t& cnf) { + MYSQL* proxy = mysql_init(NULL); + + bool reconnect = 1; + int cflags = 0; + + if (cnf.ssl) { +#ifdef LIBMYSQL_HELPER8 + enum mysql_ssl_mode ssl_mode = SSL_MODE_REQUIRED; + mysql_options(proxy, MYSQL_OPT_SSL_MODE, &ssl_mode); +#else + mysql_ssl_set(proxy, NULL, NULL, NULL, NULL, NULL); + cflags |= CLIENT_SSL; +#endif + } + + if (cnf.eof) { + proxy->options.client_flag |= CLIENT_DEPRECATE_EOF; + } + + mysql_options(proxy, MYSQL_OPT_RECONNECT, &reconnect); + cflags |= CLIENT_REMEMBER_OPTIONS; + + const string TG_BACKEND { get_env_str("TG_BACKEND", "PROXYSQL") }; + + const char* user = cl.username; + const char* pass = cl.password; + const char* host = cl.host; + int port = cl.port; + + if (TG_BACKEND == "MYSQL") { + port = cl.mysql_port; + } + + diag( + "Creating initial conn against ProxySQL host:'%s', port:'%d', user:'%s', pass:'%s'", + cl.host, cl.port, cl.username, cl.password + ); + + if (!mysql_real_connect(proxy, host, user, pass, NULL, port, NULL, cflags)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy)); + return EXIT_FAILURE; + } + +#ifdef LIBMYSQL_HELPER8 + void* ssl_session_data = nullptr; + + if (cnf.ssl) { + ssl_session_data = mysql_get_ssl_session_data(proxy, 0, nullptr); + if (ssl_session_data) { + mysql_options(proxy, MYSQL_OPT_SSL_SESSION_DATA, ssl_session_data); + } + } +#endif + + const char* admin_user = cl.admin_username; + const char* admin_pass = cl.admin_password; + const char* admin_host = cl.admin_host; + int admin_port = cl.admin_port; + + if (TG_BACKEND == "MYSQL") { + admin_user = cl.mysql_username; + admin_pass = cl.mysql_password; + admin_port = cl.mysql_port; + } + + MYSQL* admin = mysql_init(NULL); + + diag( + "Creating Admin conn against ProxySQL host:'%s', port:'%d', user:'%s', pass:'%s'", + admin_host, admin_port, admin_user, admin_pass + ); + if (!mysql_real_connect(admin, admin_host, admin_user, admin_pass, NULL, admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); + return EXIT_FAILURE; + } + + pid_t pid = getpid(); + const string s_pid { std::to_string(pid) }; + + std::thread query_thread([proxy, s_pid] () { + const string query { "/* client_pid=" + s_pid + " */ SELECT SLEEP(20)" }; + int rc = mysql_query(proxy, query.c_str()); + + ok(rc != 0, "Query should exit with error rc:%d, err:'%s'", rc, mysql_error(proxy)); + + rc = mysql_query(proxy, "DO 1"); + if (rc) { + diag("Simple query failed after reconnect query:'%s', err:'%s'", "DO 1", mysql_error(proxy)); + } + + ok(rc == 0, "Second query should succeed (reconnect) rc:%d, err:'%s'", rc, mysql_error(proxy)); + }); + + const string cond_query { + TG_BACKEND == "PROXYSQL" ? + "SELECT IIF(" + "(SELECT COUNT(*) FROM stats_mysql_processlist WHERE" + " info LIKE '%client_pid=" + s_pid + "%')=1, 'TRUE', 'FALSE')" : + "SELECT IF(" + "(SELECT COUNT(*) FROM information_schema.processlist WHERE" + " info LIKE '%client_pid=" + s_pid + "%' AND state='User sleep')=1, 'TRUE', 'FALSE')" + }; + + int wres = wait_for_cond(admin, cond_query.c_str(), 60); + + const string ext_query { + TG_BACKEND == "PROXYSQL" ? + "SELECT SessionID FROM stats_mysql_processlist WHERE info LIKE '%client_pid=" + s_pid + "%'" : + "SELECT ID FROM information_schema.processlist WHERE info LIKE '%client_pid=" + s_pid + "%'" + " AND state='User sleep'" + }; + + ext_val_t ext_sess_id = mysql_query_ext_val(admin, ext_query, int64_t(0)); + + if (ext_sess_id.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, ext_sess_id) }; + diag("Failed getting 'SessionID' query:`%s`, err:`%s`", ext_query.c_str(), err.c_str()); + goto cleanup; + } + + { + const string kill_sess_query { "KILL CONNECTION " + std::to_string(ext_sess_id.val) }; + mysql_query(admin, kill_sess_query.c_str()); + } + +cleanup: + + { + query_thread.join(); + + mysql_close(admin); +#ifdef LIBMYSQL_HELPER8 + if (ssl_session_data) { + mysql_free_ssl_session_data(proxy, ssl_session_data); + } +#endif + mysql_close(proxy); + } + + return EXIT_SUCCESS; +} + + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return EXIT_FAILURE; + } + + const auto& bin_vecs { get_all_bin_vec(2) }; + plan(bin_vecs.size() * 2); + + for (const vector vec : bin_vecs) { + _conn_cnf_t conf { vec[0], vec[1] }; + diag("Testing reconnect with config ssl:%d, eof:%d", conf.ssl, conf.eof); + + int rc = test_reconnect(cl, conf); + if (rc) { + diag("Reconnect failed, aborting further testing... rc:%d, ssl:%d, eof:%d", rc, conf.ssl, conf.eof); + break; + } + } + + return exit_status(); +} diff --git a/test/tap/tests/prepare_statement_err3024-t.cpp b/test/tap/tests/prepare_statement_err3024-t.cpp index 3938d4cf1e..3c4a1e0645 100644 --- a/test/tap/tests/prepare_statement_err3024-t.cpp +++ b/test/tap/tests/prepare_statement_err3024-t.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include "mysql.h" #include "tap.h" @@ -56,72 +55,6 @@ static int wait_for_mysql(MYSQL *mysql, int status) { } #endif -/** - * @brief Function is required to be duplicated in the test because multiple compilations using - * 'libmysqlclient' and 'libmariadbclient'. TODO: Being able to share helper functions targetting different - * connector libraries between tap tests. - */ -int add_more_rows_test_sbtest1(int num_rows, MYSQL *mysql, bool sqlite) { - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_int_distribution dist(0.0, 9.0); - - diag("Creating %d rows in sbtest1", num_rows); - while (num_rows) { - std::stringstream q; - - if (sqlite==false) { - q << "INSERT INTO test.sbtest1 (k, c, pad) values "; - } else { - q << "INSERT INTO sbtest1 (k, c, pad) values "; - } - bool put_comma = false; - int i=0; - unsigned int cnt=5+rand()%50; - if (cnt > num_rows) cnt = num_rows; - for (i=0; i honest_requests { { { "partial_output_flush_script", "%s.py", "POST", 10000 }, { "{}" } }, }; -const vector invalid_requests { +vector invalid_requests { // Checks that 'POST' fails for: // 1 - Empty parameters. // 2 - Invalid JSON input. @@ -279,6 +279,17 @@ int main(int argc, char** argv) { vector i_epts_info {}; const auto ext_i_epts_info = [] (const faulty_req_t& req) { return req.ept_info; }; + + // Failed scripts may require to read the full output from ASAN leaks report. This in combination with the + // forked process shutdown slowdown can take a considerable amount of time. A cleaner solution would be + // to disable 'detect_leaks' at runtime, but doesn't look feasible at the moment. + int wasan = get_env_int("WITHASAN", 0); + if (wasan) { + for (auto& req : invalid_requests) { + req.ept_info.timeout += 8000; + } + } + std::transform( invalid_requests.begin(), invalid_requests.end(), std::back_inserter(i_epts_info), ext_i_epts_info ); diff --git a/test/tap/tests/reg_test_3247-mycli_support-t.cpp b/test/tap/tests/reg_test_3247-mycli_support-t.cpp index 4116bd621d..329e9ad908 100644 --- a/test/tap/tests/reg_test_3247-mycli_support-t.cpp +++ b/test/tap/tests/reg_test_3247-mycli_support-t.cpp @@ -4,12 +4,9 @@ */ #include -#include #include -#include #include #include "mysql.h" -#include "mysqld_error.h" #include "tap.h" #include "command_line.h" @@ -17,18 +14,6 @@ using std::string; -std::vector split(const std::string& s, char delimiter) -{ - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) - { - tokens.push_back(token); - } - return tokens; -} - int main(int argc, char** argv) { CommandLine cl; diff --git a/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp b/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp index b5a1fc3166..caa4a50678 100644 --- a/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp +++ b/test/tap/tests/reg_test_3606-mysql_warnings-t.cpp @@ -10,7 +10,7 @@ * * Multistatements queries mixed with the previous ones are also properly executed. */ -#include +#include #include #include #include @@ -19,7 +19,6 @@ #include #include "mysql.h" -#include "mysqld_error.h" #include "proxysql_utils.h" diff --git a/test/tap/tests/reg_test_3765_ssl_pollout-t.cpp b/test/tap/tests/reg_test_3765_ssl_pollout-t.cpp index 4a42904d97..898c56f4ed 100644 --- a/test/tap/tests/reg_test_3765_ssl_pollout-t.cpp +++ b/test/tap/tests/reg_test_3765_ssl_pollout-t.cpp @@ -109,6 +109,11 @@ int main(int argc, char** argv) { plan(6); + // For ASAN builds we don't care about correctness in this measurement. + if (get_env_int("WITHASAN", 0)) { + MAX_ALLOWED_CPU_USAGE = 80; + } + double idle_cpu_ms = 0; double final_cpu_ms = 0; diff --git a/test/tap/tests/reg_test_4402-mysql_fields-t.cpp b/test/tap/tests/reg_test_4402-mysql_fields-t.cpp index 7ab0a0bbdb..bf1c0a1c16 100644 --- a/test/tap/tests/reg_test_4402-mysql_fields-t.cpp +++ b/test/tap/tests/reg_test_4402-mysql_fields-t.cpp @@ -8,6 +8,9 @@ #include #include #include + +#include "mysql.h" + #include "tap.h" #include "command_line.h" #include "utils.h" diff --git a/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp b/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp index 626545055b..906c89f998 100644 --- a/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp +++ b/test/tap/tests/reg_test_fast_forward_split_packet-t.cpp @@ -13,10 +13,10 @@ * iterations of 'MySQL_Session::handler', which will force the 'CONNECTING_SERVER' situation. */ -#include +#include +#include #include #include -#include #include #include "mysql.h" diff --git a/test/tap/tests/reg_test_mariadb_stmt_store_result-t.cpp b/test/tap/tests/reg_test_mariadb_stmt_store_result-t.cpp index 292f98f137..aabf1c0e27 100644 --- a/test/tap/tests/reg_test_mariadb_stmt_store_result-t.cpp +++ b/test/tap/tests/reg_test_mariadb_stmt_store_result-t.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include "mysql.h" #include "tap.h" @@ -49,72 +48,6 @@ static int wait_for_mysql(MYSQL *mysql, int status) { } #endif -/** - * @brief Function is required to be duplicated in the test because multiple compilations using - * 'libmysqlclient' and 'libmariadbclient'. TODO: Being able to share helper functions targetting different - * connector libraries between tap tests. - */ -int add_more_rows_test_sbtest1(int num_rows, MYSQL *mysql, bool sqlite) { - std::random_device rd; - std::mt19937 mt(rd()); - std::uniform_int_distribution dist(0.0, 9.0); - - diag("Creating %d rows in sbtest1", num_rows); - while (num_rows) { - std::stringstream q; - - if (sqlite==false) { - q << "INSERT INTO test.sbtest1 (k, c, pad) values "; - } else { - q << "INSERT INTO sbtest1 (k, c, pad) values "; - } - bool put_comma = false; - int i=0; - unsigned int cnt=5+rand()%50; - if (cnt > num_rows) cnt = num_rows; - for (i=0; i #include #include -#include "mysql.h" #include #include #include #include #include #include -#include -#include -#include #include +#include "mysql.h" #include "json.hpp" -#include "re2/re2.h" -#include "re2/regexp.h" #include "tap.h" #include "utils.h" @@ -116,7 +111,8 @@ std::unordered_map vars_counters; */ void * my_conn_thread(void *arg) { - g_seed = time(NULL) ^ getpid() ^ pthread_self(); + g_seed = monotonic_time() * pthread_self() + monotonic_time(); + srand(g_seed); unsigned int select_OK=0; unsigned int select_ERR=0; int i, j; @@ -168,7 +164,7 @@ void * my_conn_thread(void *arg) { int fr = rand(); int r1=fr%count; //int r2=fastrand()%testCases.size(); - int r2=rand()%testCases.size(); + int r2=(fastrand() + (RAND_MAX * fastrand())) %testCases.size(); if (j%queries_per_connections==0) { mysql_idx=r1; diff --git a/test/tap/tests/set_testing-240.h b/test/tap/tests/set_testing-240.h index 60254da37e..5375648430 100644 --- a/test/tap/tests/set_testing-240.h +++ b/test/tap/tests/set_testing-240.h @@ -1,14 +1,6 @@ -std::vector split(const std::string& s, char delimiter) -{ - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) - { - tokens.push_back(token); - } - return tokens; -} +#include +#include +#include "json.hpp" using nlohmann::json; @@ -146,7 +138,7 @@ struct cpu_timer inline int fastrand() { - g_seed = (214014*g_seed+2531011); + g_seed = (214013*g_seed+2531011); return (g_seed>>16)&0x7FFF; } diff --git a/test/tap/tests/set_testing-multi-t.cpp b/test/tap/tests/set_testing-multi-t.cpp index 1f85d6a95b..845ce0be9c 100644 --- a/test/tap/tests/set_testing-multi-t.cpp +++ b/test/tap/tests/set_testing-multi-t.cpp @@ -1,21 +1,18 @@ #include #include #include -#include "mysql.h" #include #include #include #include #include #include -#include #include -#include #include +#include "mysql.h" #include "json.hpp" #include "re2/re2.h" -#include "re2/regexp.h" #include "tap.h" #include "utils.h" diff --git a/test/tap/tests/set_testing.h b/test/tap/tests/set_testing.h index b93d036d44..f6c8e65309 100644 --- a/test/tap/tests/set_testing.h +++ b/test/tap/tests/set_testing.h @@ -1,14 +1,6 @@ -std::vector split(const std::string& s, char delimiter) -{ - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) - { - tokens.push_back(token); - } - return tokens; -} +#include +#include +#include "json.hpp" using nlohmann::json; diff --git a/test/tap/tests/test_auth_methods-t.cpp b/test/tap/tests/test_auth_methods-t.cpp index 306b8e4dbd..d12aedcd7f 100644 --- a/test/tap/tests/test_auth_methods-t.cpp +++ b/test/tap/tests/test_auth_methods-t.cpp @@ -31,8 +31,8 @@ #include #include -#include "proxysql_utils.h" #include "openssl/ssl.h" +#include "json.hpp" #include "mysql.h" #include "tap.h" diff --git a/test/tap/tests/test_backend_conn_ping-t.cpp b/test/tap/tests/test_backend_conn_ping-t.cpp index 5c2be5634c..1e795d97d5 100644 --- a/test/tap/tests/test_backend_conn_ping-t.cpp +++ b/test/tap/tests/test_backend_conn_ping-t.cpp @@ -11,24 +11,66 @@ * over time. I.e. connections are not getting destroyed due to not being kept alive. * 3. Perform a query per each created connection exahusting the backend connections while checking for * any error reported by the client due to broken connections. + * + * Backend Connection Pinging Algorithm: + * ==================================== + * + * The algorithm used for pinging backend connections is simple: + * + * 1. Pinging is done for 'idle_session' in batches of SESSIONS_FOR_CONNECTIONS_HANDLER. + * 2. The pinging interval is determined by 'ping_interval_server_msec'. + * 3. Oldest sessions are always selected first for pinging. + * + * With this approach, it should be possible to ping a very large number of backend connections before + * losing them due to inactivity (wait_timeout), this will be defined by: + * + * ``` + * (NUM_THREADS * SESSIONS_FOR_CONNECTIONS_HANDLER)*floor((MYSQL_wait_timeout-1)/ping_interval_server_msec) + * ``` + * + * This means that, for example, for a config like: `num_threads=4,wait_timeout=60,batch_size=64`, we can + * expect: `15104` connections. Of course, this suggests that this algorithm wont have any issues holding a + * healthy connection pool of any size when using real `wait_timeout` values. + * + * Algorithm - Special Cases: + * ========================= + * + * This algorithm has a expected behavior that might seems unexpected when pushing the limits of the + * connections that are being maintained; like we can accidentally do in this test. If the number of + * connections being maintained exceeds the number that can be pinged within the `MySQL_wait_timeout` + * interval, this will cause a cascading effect, that will result in extra connections timing out. The + * number of connections from the connection pool that will timeout will be proportional to the fraction of + * the batch (NUM_THREADS*SESSIONS_FOR_CONNECTIONS_HANDLER) filled with connections that exceeded the + * interval processing capacity. + * + * If for example, the number of connections exceeded the ones that can be maintained by 50% of the batching + * interval, for the case of 4 threads, this will be 128 connections, then 50% of the connection pool will + * be lost due to this cascading effect. Elaborating a little bit further, when the number of connections + * being maintained exceeds the number of connections that can be pinged, the connections exceeding the + * interval capabilities will timeout, since these connections will be the oldest, they will be the first to + * be selected for the next interval. This will shift all subsequent batching intervals by this number of + * connections, since we are operating at maximum capacity (maximum number exceeded), all the intervals were + * selecting connections that should be pinged, otherwise will timeout. This is why the previously mentioned + * shift will result in the timeout of these connections, causing the mentioned cascading effect. + * + * This is expected behavior, and **it's not an issue**. The number of connections ProxySQL can ping is + * mainly determined by the interval that defines `wait_timeout` in the MySQL server and + * `ping_interval_server_msec`, which default value is `1000`. With reasonable values for these two + * parameters this number is very high, even with extremely low values for `wait_timeout`, like + * `num_threads=4,wait_timeout=60,batch_size=64`, a large number of connections(`15104`) would still be + * maintained without issues. This is what makes this issue an artificial one. */ -/* -NOTE: the parameters in this test are tuned in a way that if proxysql starts -with only 1 worker thread, it is unlikely to ping all connections on time. -See note on wait_timeout -*/ - #include #include #include #include -#include #include #include #include +#include "mysql.h" #include "tap.h" #include "utils.h" #include "json.hpp" @@ -39,49 +81,20 @@ using std::pair; using srv_cfg = vector>; -int wait_timeout = 10; - -#ifndef SESSIONS_FOR_CONNECTIONS_HANDLER -#define SESSIONS_FOR_CONNECTIONS_HANDLER 64 -#endif +#define SESSIONS_FOR_CONNECTIONS_HANDLER 64 -// if only 1 worker thread is running, wait_timeout should be bigger -// 1 worker thread : wait_timeout = 45 -// 4 worker threads : wait_timeout = 10 -int compute_wait_timeout(MYSQL *my_conn) { - int res = EXIT_SUCCESS; - res = mysql_query(my_conn, "SELECT @@mysql-threads"); - if (res != EXIT_SUCCESS) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(my_conn)); - res = EXIT_FAILURE; - return res; - } - MYSQL_RES* my_res = mysql_store_result(my_conn); - if (my_res == nullptr) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(my_conn)); - res = EXIT_FAILURE; - return res; - } +// IMPORTANT: We **always** gives ourselves `1` second grace period. Depending on MySQL connection killing +// policy for `wait_timeout`, connections could already be killed on the edge of the interval. Also, for extra +// safety, we gave `1` extra second to avoid possible rounding errors on time computations within MySQL. +uint32_t grace_period = 2; +int wait_timeout = 10 + grace_period; - MYSQL_ROW row = mysql_fetch_row(my_res); - if (row == nullptr || row[0] == nullptr) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(my_conn)); - res = EXIT_FAILURE; - return res; - } else { - const char *val = row[0]; - diag("mysql-threads = %s", val); - if (strcmp(val,"1")==0) { - diag("Setting wait_timeout to 45 instead of 10"); - wait_timeout = 45; - } - } - mysql_free_result(my_res); +int max_pinged_conns(uint32_t num_threads, uint32_t ping_interval_server_msec, uint32_t wait_timeout) { + uint32_t ping_proc_batches = floor((wait_timeout- (grace_period+1))/float(ping_interval_server_msec/1000.0)); - return res; + return (num_threads * SESSIONS_FOR_CONNECTIONS_HANDLER) * ping_proc_batches; } - int change_mysql_cfg( const CommandLine& cl, const string& host, const string& port, const srv_cfg& new_srv_cfg, srv_cfg& out_old_srv_cfg ) { @@ -233,7 +246,6 @@ int check_backend_conns( // 2. Check that the connections remain steady for a period of time MYSQL* admin = mysql_init(NULL); - vector svrs_conns {}; { if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { @@ -241,19 +253,6 @@ int check_backend_conns( return EXIT_FAILURE; } - for (const auto& svr_addr : svrs_addrs) { - MYSQL* mysql = mysql_init(NULL); - -// if (!mysql_real_connect(mysql, svr_addr.first.c_str(), cl.username, cl.password, NULL, svr_addr.second, NULL, 0)) { - if (!mysql_real_connect(mysql, svr_addr.first.c_str(), cl.mysql_username, cl.mysql_password, NULL, svr_addr.second, NULL, 0)) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); - res = EXIT_FAILURE; - goto cleanup; - } - - svrs_conns.push_back(mysql); - } - sleep(5); uint64_t exp_conn_count = test_params.init_batch_size + test_params.batch_size * test_params.its; @@ -263,8 +262,8 @@ int check_backend_conns( uint64_t act_proxy_free_conn_count = 0; uint64_t act_proxy_used_conn_count = 0; + uint32_t intv = 5; uint32_t total_wait_time = 40; - uint32_t intv = 10; uint32_t total_checks = total_wait_time / intv; for (uint32_t check_num = 0; check_num < total_checks; check_num++) { @@ -272,12 +271,30 @@ int check_backend_conns( act_mysql_conn_count = 0; const string mysql_query_string { - "SELECT count(*) FROM information_schema.processlist WHERE" + "SELECT count(*) FROM INFORMATION_SCHEMA.PROCESSLIST WHERE" " USER=\"" + string { cl.username } + "\"" //" USER=\"" + string { cl.username } + "\" and DB=\"backend_conn_ping_test\"" //" COMMAND=\"Sleep\" and USER=\"" + string { cl.username } + "\" and DB=\"backend_conn_ping_test\"" }; diag("Line:%d : Running: %s", __LINE__ , mysql_query_string.c_str()); + + vector svrs_conns {}; + + // Recreate the connections at each interval + for (const auto& svr_addr : svrs_addrs) { + const char* c_svr_addr { svr_addr.first.c_str() }; + + MYSQL* mysql = mysql_init(NULL); + + if (!mysql_real_connect(mysql, c_svr_addr, cl.mysql_username, cl.mysql_password, NULL, svr_addr.second, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(mysql)); + res = EXIT_FAILURE; + goto cleanup; + } + + svrs_conns.push_back(mysql); + } + for (MYSQL* mysql : svrs_conns) { uint64_t tmp_mysql_conn_count = 0; @@ -290,6 +307,10 @@ int check_backend_conns( act_mysql_conn_count += tmp_mysql_conn_count; } + for (MYSQL* mysql : svrs_conns) { + mysql_close(mysql); + } + const string srv_ports { std::accumulate(std::begin(svrs_addrs), std::end(svrs_addrs), string {}, [](const string& s1, const svr_addr& addr) -> string { @@ -342,12 +363,6 @@ int check_backend_conns( diag("act_proxy_free_conn_count = %lu", act_proxy_free_conn_count); diag("act_proxy_used_conn_count = %lu", act_proxy_used_conn_count); - if ( - act_mysql_conn_count >= exp_conn_count || - (act_proxy_free_conn_count + act_proxy_used_conn_count + SESSIONS_FOR_CONNECTIONS_HANDLER) >= exp_conn_count - ) { - break; - } if (intv) { diag("Line:%d : Sleeping %d" , __LINE__ , intv); sleep(intv); @@ -357,9 +372,7 @@ int check_backend_conns( ok( q_res == EXIT_SUCCESS && act_mysql_conn_count >= ((float) exp_conn_count * 0.95) // allow 5% margin of error && - ((act_proxy_free_conn_count + act_proxy_used_conn_count + SESSIONS_FOR_CONNECTIONS_HANDLER) >= exp_conn_count) - //&& act_mysql_conn_count == act_proxy_free_conn_count // they can't be equal - , + ((act_proxy_free_conn_count + act_proxy_used_conn_count) >= exp_conn_count), "Created server connections should be properly maintained (pinged) by ProxySQL:" " { ExpConns: %ld, ActMySQLConns: %ld, ActProxyConns: %ld }", exp_conn_count, act_mysql_conn_count, act_proxy_free_conn_count @@ -390,9 +403,6 @@ int check_backend_conns( cleanup: mysql_close(admin); - for (MYSQL* mysql : svrs_conns) { - mysql_close(mysql); - } for (MYSQL* mysql : mysql_conns) { mysql_close(mysql); @@ -457,38 +467,65 @@ int main(int, char**) { // Close no longer required connection mysql_close(proxy_mysql); - MYSQL* proxy_admin = mysql_init(NULL); - if (!proxy_admin) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_admin)); - return exit_status(); - } - - if (!mysql_real_connect(proxy_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { - fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_admin)); + MYSQL* admin = mysql_init(NULL); + if (!admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); return exit_status(); } - if (compute_wait_timeout(proxy_admin) != EXIT_SUCCESS) { + if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); return exit_status(); } - double intv = 5; - double b = 128; double b_0 = 256; + double b = 128; double freq = 1000; - double rate = 64 / ( freq / 1000 ); - double conn_creation_intv = 30; + double rate = 64 / (freq / 1000); + + const char q_mysql_threads[] { "SELECT @@mysql-threads" }; + ext_val_t ext_threads { mysql_query_ext_val(admin, q_mysql_threads, int32_t(0)) }; + + if (ext_threads.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, ext_threads) }; + diag("Failed query query:`%s`, err:`%s`", q_mysql_threads, err.c_str()); + return EXIT_FAILURE; + } + + // IMPORTANT-NOTE: + // Variable 'ping_interval_server_msec' isn't relevant because ProxySQL isn't under load. The relevant + // variable passes to be `mysql-poll_timeout`; with each timeout threads will process idle sessions. If we + // wished to extend this test with a dummy load on ProxySQL, this should be uncommented. + // =================================================================================================== + // const char q_ping_intv[] { "SELECT @@mysql-ping_interval_server_msec" }; + const char q_ping_intv[] { "SELECT @@mysql-poll_timeout" }; + // =================================================================================================== + ext_val_t ext_ping_intv { mysql_query_ext_val(admin, q_ping_intv, int32_t(0)) }; - double its = (conn_creation_intv - b_0/rate) / ( b / rate ); + if (ext_ping_intv.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, ext_ping_intv) }; + diag("Failed query query:`%s`, err:`%s`", q_ping_intv, err.c_str()); + return EXIT_FAILURE; + } + + uint32_t max_conns = max_pinged_conns(ext_threads.val, 2000, wait_timeout); + uint32_t server_max_conns = max_conns + 1000; + double its = floor((max_conns - b_0) / b); + + double conn_creation_intv = b_0/rate + (b/rate)*its; double delay_s = conn_creation_intv / its; + diag("ProxySQL config mysql_threads:%d, ping_intv:%d", ext_threads.val, ext_ping_intv.val); + diag("Selected test params b_0:%lf, b:%lf, freq:%lf, rate:%lf", b_0, b, freq, rate); + diag("Computed test params max_conns=%d, intv=%lf, its=%lf", max_conns, conn_creation_intv, its); + // Cleanup previous backend connections diag("Cleaning up previous backend connections..."); - MYSQL_QUERY(proxy_admin, "UPDATE mysql_servers SET max_connections=0"); - MYSQL_QUERY(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + MYSQL_QUERY(admin, "UPDATE mysql_servers SET max_connections=0"); + MYSQL_QUERY(admin, "LOAD MYSQL SERVERS TO RUNTIME"); // Wait for backend connection cleanup - int w_res = wait_target_backend_conns(proxy_admin, 0, 10); + int w_res = wait_target_backend_conns(admin, 0, 10); if (w_res != EXIT_SUCCESS) { if (w_res == -2) { const char* err_msg = "'wait_target_backend_conns()' timed out"; @@ -499,12 +536,12 @@ int main(int, char**) { } diag("Setting mysql_servers config..."); { - string query = "UPDATE mysql_servers SET max_connections=2500"; + string query { "UPDATE mysql_servers SET max_connections=" + std::to_string(server_max_conns) }; diag("Running: %s", query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); query = "LOAD MYSQL SERVERS TO RUNTIME"; diag("Running: %s", query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); } diag("Setting ProxySQL config..."); @@ -512,27 +549,30 @@ int main(int, char**) { // Set the backend connections ping frequency string query = string { "SET mysql-ping_interval_server_msec=" + std::to_string(freq) }; diag("%s", query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); // Make sure no connection cleanup takes place query = "SET mysql-free_connections_pct=100"; diag("%s", query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); // Don't retry on failure query = "SET mysql-query_retries_on_failure=0"; diag("%s", query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); + query = "SET mysql-max_connections=" + std::to_string(server_max_conns*3); + diag("%s", query.c_str()); + MYSQL_QUERY(admin, query.c_str()); // Set a higher max_connection number for the servers query = "LOAD MYSQL VARIABLES TO RUNTIME"; diag("%s", query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); } // Configure MySQL infra servers with: 'wait_timeout' and 'max_connections' vector> servers_old_configs {}; diag("Configure 'MYSQL' infra servers..."); { - MYSQL_QUERY(proxy_admin, "SELECT DISTINCT hostname, port FROM mysql_servers WHERE hostgroup_id IN (0,1)"); - MYSQL_RES* my_servers_res = mysql_store_result(proxy_admin); + MYSQL_QUERY(admin, "SELECT DISTINCT hostname, port FROM mysql_servers WHERE hostgroup_id IN (0,1)"); + MYSQL_RES* my_servers_res = mysql_store_result(admin); vector servers_rows = extract_mysql_rows(my_servers_res); mysql_free_result(my_servers_res); @@ -541,7 +581,7 @@ int main(int, char**) { return exit_status(); } - srv_cfg new_srv_cfg { { "wait_timeout", wait_timeout }, { "max_connections", 2500 } }; + srv_cfg new_srv_cfg { { "wait_timeout", wait_timeout }, { "max_connections", 5000 } }; for (const mysql_res_row& srv_row : servers_rows) { srv_cfg old_srv_cfg {}; @@ -565,7 +605,11 @@ int main(int, char**) { const string docker_mode = getenv("DOCKER_MODE"); if (docker_mode.find("dns") == docker_mode.size() - 3) { s_server_test.assign({ { "mysql1.infra-mysql57", 3306 } }); - m_server_test.assign({ { "mysql1.infra-mysql57", 3306 }, { "mysql2.infra-mysql57", 3306 }, { "mysql3.infra-mysql57", 3306 } }); + m_server_test.assign({ + { "mysql1.infra-mysql57", 3306 }, + { "mysql2.infra-mysql57", 3306 }, + { "mysql3.infra-mysql57", 3306 } + }); } else { s_server_test.assign({ { "127.0.0.1", 13306 } }); m_server_test.assign({ { "127.0.0.1", 13306 }, { "127.0.0.1", 13307 }, { "127.0.0.1", 13308 } }); @@ -584,12 +628,12 @@ int main(int, char**) { diag("Cleaning up previous backend connections..."); string query = "UPDATE mysql_servers SET max_connections=0"; diag("Line:%d : Running: %s", __LINE__ , query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); query = "LOAD MYSQL SERVERS TO RUNTIME"; diag("Line:%d : Running: %s", __LINE__ , query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); - int w_res = wait_target_backend_conns(proxy_admin, 0, 10); + int w_res = wait_target_backend_conns(admin, 0, 10); if (w_res != EXIT_SUCCESS) { string err_msg {}; if (w_res == -2) { @@ -600,12 +644,13 @@ int main(int, char**) { fprintf(stderr, "File %s, line %d, Error: \"%s\"\n", __FILE__, __LINE__, err_msg.c_str()); } - query = "UPDATE mysql_servers SET max_connections=2500"; + diag("Reconfiguring ProxySQL 'mysql_servers' after connection cleanup"); + query = "UPDATE mysql_servers SET max_connections=" + std::to_string(server_max_conns); diag("Line:%d : Running: %s", __LINE__ , query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); query = "LOAD MYSQL SERVERS TO RUNTIME"; diag("Line:%d : Running: %s", __LINE__ , query.c_str()); - MYSQL_QUERY(proxy_admin, query.c_str()); + MYSQL_QUERY(admin, query.c_str()); if (w_res == EXIT_SUCCESS) { diag("Performing 'check_backend_conns()' for servers: '%s'", nlohmann::json(m_server_test).dump().c_str()); @@ -634,7 +679,7 @@ int main(int, char**) { } } - mysql_close(proxy_admin); + mysql_close(admin); return exit_status(); } diff --git a/test/tap/tests/test_cacert_load_and_verify_duration-t.cpp b/test/tap/tests/test_cacert_load_and_verify_duration-t.cpp index 19da85d3d0..f75b913ee0 100644 --- a/test/tap/tests/test_cacert_load_and_verify_duration-t.cpp +++ b/test/tap/tests/test_cacert_load_and_verify_duration-t.cpp @@ -24,6 +24,10 @@ int main() { plan(1); + int32_t WASAN = get_env_int("WITHASAN", 0); + // Double the value of previously failed ASAN run: '89415ms' + int32_t EXP_TIME = WASAN == 0 ? 20000 : 180000; + MYSQL* proxysql_admin = mysql_init(NULL); // Initialize connection @@ -57,7 +61,7 @@ int main() { if (start_pos != std::string::npos && end_pos != std::string::npos) { uint64_t time = std::stoull(msg.substr(start_pos + 5, end_pos - (start_pos + 5))); - ok(time < 20000, "Total duration is '%lu ms' should be less than 20 Seconds", time); + ok(time < EXP_TIME, "Total duration is '%lu ms' should be less than %d Seconds", time, EXP_TIME/1000); } } mysql_close(proxysql_admin); diff --git a/test/tap/tests/test_cluster_sync-t.cpp b/test/tap/tests/test_cluster_sync-t.cpp index 505fb6dacb..db992be696 100644 --- a/test/tap/tests/test_cluster_sync-t.cpp +++ b/test/tap/tests/test_cluster_sync-t.cpp @@ -575,6 +575,8 @@ const vector module_sync_payloads { }; int wait_for_node_sync(MYSQL* admin, const vector queries, uint32_t timeout) { + diag("Starting wait for node synchronization"); + uint waited = 0; bool not_synced = false; std::string failed_query {}; @@ -584,7 +586,7 @@ int wait_for_node_sync(MYSQL* admin, const vector queries, uint32_t time // Check that all the entries have been synced for (const auto& query : queries) { - if (mysql_query(admin, query.c_str())) { + if (mysql_query_t(admin, query.c_str())) { fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin)); return -1; } @@ -681,6 +683,32 @@ string fetch_runtime_checksum(MYSQL* admin, const string& module) { const int def_mod_diffs_sync = 2; +int32_t get_checksum_sync_timeout(MYSQL* admin) { + const char q_check_intv[] { + "SELECT variable_value FROM global_variables WHERE variable_name='admin-cluster_check_interval_ms'" + }; + ext_val_t ext_check_intv { mysql_query_ext_val(admin, q_check_intv, int64_t(0)) }; + + if (ext_check_intv.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, ext_check_intv) }; + diag("Failed getting 'cluster_check_interval_ms' query:`%s`, err:`%s`", q_check_intv, err.c_str()); + return -1; + } + + const char q_sts_freq[] { + "SELECT variable_value FROM global_variables WHERE variable_name='admin-cluster_check_status_frequency'" + }; + ext_val_t ext_sts_freq { mysql_query_ext_val(admin, q_sts_freq, int64_t(0)) }; + + if (ext_sts_freq.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, ext_check_intv) }; + diag("Failed getting 'cluster_check_status_frequency' query:`%s`, err:`%s`", q_check_intv, err.c_str()); + return -1; + } + + return ((ext_check_intv.val/1000) * ext_sts_freq.val) + 1; +} + int check_module_checksums_sync( MYSQL* admin, MYSQL* r_admin, @@ -691,7 +719,7 @@ int check_module_checksums_sync( ) { const char new_remote_checksum_query_t[] { "SELECT count(*) FROM stats_proxysql_servers_checksums WHERE " - "hostname='%s' AND port='%d' AND name='%s' AND checksum!='%s'" + "hostname='%s' AND port='%d' AND name='%s' AND checksum!='%s' AND checksum='%s'" }; const char synced_runtime_checksums_query_t[] { "SELECT COUNT(*) FROM runtime_checksums_values WHERE name='%s' AND checksum='%s'" @@ -701,7 +729,11 @@ int check_module_checksums_sync( const string& module { module_sync.module }; // Checksum can not be present if we have just added the remote - uint32_t CHECKSUM_SYNC_TIMEOUT = 3; + uint32_t CHECKSUM_SYNC_TIMEOUT = get_checksum_sync_timeout(admin); + if (CHECKSUM_SYNC_TIMEOUT == -1) { + diag("Failed fetching values to compute 'CHECKSUM_SYNC_TIMEOUT'"); + return EXIT_FAILURE; + } const char wait_remote_checksums_init_t[] { "SELECT LENGTH(checksum) FROM stats_proxysql_servers_checksums WHERE " @@ -711,7 +743,7 @@ int check_module_checksums_sync( cstr_format(wait_remote_checksums_init_t, conn_opts.host.c_str(), conn_opts.port, module.c_str()) }; - int checksum_present = wait_for_node_sync( r_admin, { wait_remote_checksums_init.str }, CHECKSUM_SYNC_TIMEOUT); + int checksum_present = wait_for_node_sync(r_admin, { wait_remote_checksums_init.str }, CHECKSUM_SYNC_TIMEOUT); if (checksum_present) { diag("No checksum (or zero) detected int the target remote server for module '%s'", module.c_str()); return EXIT_FAILURE; @@ -736,6 +768,20 @@ int check_module_checksums_sync( return EXIT_FAILURE; } + // Get the new checksum computed after previous 'UPDATE' operation + const char q_module_checksum_t[] { + "SELECT checksum FROM main.runtime_checksums_values WHERE name='%s'" + }; + + cfmt_t q_module_checksum { cstr_format(q_module_checksum_t, module.c_str()) }; + ext_val_t ext_checksum { mysql_query_ext_val(admin, q_module_checksum.str, string()) }; + + if (ext_checksum.err != EXIT_SUCCESS) { + const string err { get_ext_val_err(admin, ext_checksum) }; + diag("Failed query query:`%s`, err:`%s`", q_module_checksum.str.c_str(), err.c_str()); + return EXIT_FAILURE; + } + // Wait for new checksum to be detected cfmt_t new_remote_checksum_query { cstr_format( @@ -743,7 +789,8 @@ int check_module_checksums_sync( conn_opts.host.c_str(), conn_opts.port, module.c_str(), - cur_remote_checksum.c_str() + cur_remote_checksum.c_str(), + ext_checksum.val.c_str() ) }; int sync_res = wait_for_node_sync(r_admin, { new_remote_checksum_query.str }, CHECKSUM_SYNC_TIMEOUT); @@ -910,6 +957,19 @@ int check_all_modules_sync( const sync_payload_t& sync_payload = module_sync_payloads[j]; const int diffs_sync = j == dis_module ? 0 : def_mod_diffs_sync; + // REQUIRE-WAIT: All checks make use of the 'admin_variables' to enable/disable module + // synchronization. The previous module check only waits for the module synchronization itself, but + // not for the propagation of the changed 'admin_variables'; not waiting the propagation of this + // previous change could interfere with the checks target to this same module. + if (module_sync_payloads[j].module == "admin_variables") { + uint32_t CHECKSUM_SYNC_TIMEOUT = get_checksum_sync_timeout(admin); + if (CHECKSUM_SYNC_TIMEOUT == -1) { + diag("Failed fetching values to compute 'CHECKSUM_SYNC_TIMEOUT'"); + return EXIT_FAILURE; + } + sleep(CHECKSUM_SYNC_TIMEOUT); + } + int check_sync = check_module_checksums_sync(admin, r_admin, conn_opts, sync_payload, diffs_sync, remote_stderr); if (check_sync) { if (diffs_sync) { @@ -968,7 +1028,8 @@ int check_modules_checksums_sync( for (size_t dis_module = 0; dis_module < module_sync_payloads.size(); dis_module++) { printf("\n"); - diag("Start test with sync DISABLED for module '%s'", module_sync_payloads[dis_module].module.c_str()); + const string dis_module_str { module_sync_payloads[dis_module].module }; + diag("Start test with sync DISABLED for module '%s'", dis_module_str.c_str()); for (const sync_payload_t& sync_payload : module_sync_payloads) { const string set_query { "SET " + sync_payload.sync_variable + "=" + def_syncs }; @@ -981,9 +1042,11 @@ int check_modules_checksums_sync( MYSQL_QUERY_T(r_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); // Check that ALL modules sync, but 'dis_module' in both ways - Main-To-Remote and Remote-To-Main + diag("Checking ALL modules SYNC but DISABLED module '%s'", dis_module_str.c_str()); check_all_modules_sync(admin, r_admin, m_conn_opts.first, dis_module, main_stderr, remote_stderr); // Enable back the module + diag("Renable module '%s' synchronization", dis_module_str.c_str()); const string enable_query { "SET " + module_sync_payloads[dis_module].sync_variable + "=" + std::to_string(def_mod_diffs_sync) }; @@ -1005,15 +1068,22 @@ int check_modules_checksums_sync( } // Check that the module syncs again in both ways + diag("Checking module '%s' syncs again - MAIN to REMOTE", dis_module_str.c_str()); check_module_checksums_sync( admin, r_admin, m_conn_opts.first, module_sync_payloads[dis_module], def_mod_diffs_sync, remote_stderr ); + // A wait IS NOT required. The checks perform the required waiting, ensuring that the new computed + // checksum after the module update has been propagated to the other server. Previously the check + // didn't take into account the exact checksum, only the change, this led to invalid change + // detections. + diag("Checking module '%s' syncs again - REMOTE to MAIN", dis_module_str.c_str()); check_module_checksums_sync( r_admin, admin, r_conn_opts.first, module_sync_payloads[dis_module], def_mod_diffs_sync, main_stderr ); if (module_sync_payloads[dis_module].module != "proxysql_servers") { // Disable the module using checksums + diag("Disable module '%s' using checksums", dis_module_str.c_str()); const string disable_checksum_query { "SET " + module_sync_payloads[dis_module].checksum_variable + "=false" }; @@ -1021,6 +1091,7 @@ int check_modules_checksums_sync( MYSQL_QUERY_T(r_admin, "LOAD ADMIN VARIABLES TO RUNTIME"); // Check that ALL modules sync, but 'dis_module' in both ways - Main-To-Remote and Remote-To-Main + diag("Checking ALL modules SYNC but DISABLED module '%s'", dis_module_str.c_str()); check_all_modules_sync(admin, r_admin, m_conn_opts.first, dis_module, main_stderr, remote_stderr); // Enable back the module @@ -1044,9 +1115,11 @@ int check_modules_checksums_sync( } // Check that the module syncs again in both ways + diag("Checking module '%s' syncs again - MAIN to REMOTE", dis_module_str.c_str()); check_module_checksums_sync( admin, r_admin, m_conn_opts.first, module_sync_payloads[dis_module], def_mod_diffs_sync, remote_stderr ); + diag("Checking module '%s' syncs again - REMOTE to MAIN", dis_module_str.c_str()); check_module_checksums_sync( r_admin, admin, r_conn_opts.first, module_sync_payloads[dis_module], def_mod_diffs_sync, main_stderr ); diff --git a/test/tap/tests/test_digest_umap_aux-t.cpp b/test/tap/tests/test_digest_umap_aux-t.cpp index 227f5af08d..22391e05b8 100644 --- a/test/tap/tests/test_digest_umap_aux-t.cpp +++ b/test/tap/tests/test_digest_umap_aux-t.cpp @@ -178,7 +178,8 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - plan(1 + DUMMY_QUERIES.size() * 5); // always specify the number of tests that are going to be performed + int nplan = (1 + DUMMY_QUERIES.size() * 5); // always specify the number of tests that are going to be performed + plan(nplan); MYSQL *proxy_admin = mysql_init(NULL); if (!mysql_real_connect(proxy_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { @@ -314,6 +315,12 @@ int main(int argc, char** argv) { ); } + if (tests_last() == nplan && tests_failed == 0) { + string q = "TRUNCATE TABLE stats.stats_mysql_query_digest"; + diag("Running %s", q.c_str()); + MYSQL_QUERY(proxy_admin, q.c_str()); + } + mysql_close(proxy_admin); return exit_status(); diff --git a/test/tap/tests/test_dns_cache-t.cpp b/test/tap/tests/test_dns_cache-t.cpp index 652efa2425..7e14bfa0d2 100644 --- a/test/tap/tests/test_dns_cache-t.cpp +++ b/test/tap/tests/test_dns_cache-t.cpp @@ -6,12 +6,12 @@ #include #include -#include -#include +#include #include #include #include -#include +#include +#include #include #include "mysql.h" @@ -21,18 +21,6 @@ #include "command_line.h" #include "utils.h" -std::vector split(const std::string& s, char delimiter) { - std::vector tokens {}; - std::string token {}; - std::istringstream tokenStream(s); - - while (std::getline(tokenStream, token, delimiter)) { - tokens.push_back(token); - } - - return tokens; -} - /** * @brief Extract the metrics values from the output of the admin command * 'SHOW PROMETHEUS METRICS'. diff --git a/test/tap/tests/test_flagOUT_weight-t.cpp b/test/tap/tests/test_flagOUT_weight-t.cpp index f07da9f2cc..d81ed1f55b 100644 --- a/test/tap/tests/test_flagOUT_weight-t.cpp +++ b/test/tap/tests/test_flagOUT_weight-t.cpp @@ -1,11 +1,11 @@ #include #include #include +#include #include #include #include -#include #include "mysql.h" #include "tap.h" diff --git a/test/tap/tests/test_format_utils-t.cpp b/test/tap/tests/test_format_utils-t.cpp index 252bac5f29..41ab41179a 100644 --- a/test/tap/tests/test_format_utils-t.cpp +++ b/test/tap/tests/test_format_utils-t.cpp @@ -5,11 +5,13 @@ * supplied buffer size are properly tested. */ -#include +#include +#include #include #include #include #include +#include #include "proxysql_utils.h" #include "tap.h" diff --git a/test/tap/tests/test_hostgroup_attributes_online_servers-t.cpp b/test/tap/tests/test_hostgroup_attributes_online_servers-t.cpp new file mode 100644 index 0000000000..e6d7863a19 --- /dev/null +++ b/test/tap/tests/test_hostgroup_attributes_online_servers-t.cpp @@ -0,0 +1,143 @@ +/** + * @file test_hostgroup_attributes_online_servers-t.cpp + * @brief This test will evaluate configured maximum number of online servers within a hostgroup operates correctly. + * Note: + * This test is based on the assumption that ProxySQL is configured with read and write splitting, with writer servers in hostgroup 0, + * and readers in hostgroup 1 (having multiple servers). + */ + +#include +#include +#include +#include +#include "mysql.h" +#include "mysqld_error.h" +#include "tap.h" +#include "command_line.h" +#include "utils.h" + +#define MYSQL_QUERY__(mysql, query) \ + do { \ + if (mysql_query(mysql, query)) { \ + fprintf(stderr, "File %s, line %d, Error: %s\n", \ + __FILE__, __LINE__, mysql_error(mysql)); \ + goto cleanup; \ + } \ + } while(0) + +#define MYSQL_CLEAR_RESULT(mysql) mysql_free_result(mysql_store_result(mysql)); +#define NUM_QUERY_EXEC 5 + + +std::tuple execute_query_and_check_result(MYSQL* proxysql, const char* query, bool is_select, + bool should_succeed, unsigned int iteration = 1) { + bool res = true; + unsigned int errcode = 0; + unsigned int i; + for (i = 0; i < iteration; i++) { + const int result = mysql_query(proxysql, query); + if (result) errcode = mysql_errno(proxysql); + if (result == 0) { MYSQL_CLEAR_RESULT(proxysql); } + if ((should_succeed && result != 0) || (!should_succeed && result == 0)) { + res = false; + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + return std::make_tuple(res, errcode, i); +} + +int main(int argc, char** argv) { + CommandLine cl; + + if (cl.getEnv()) { + diag("Failed to get the required environmental variables."); + return -1; + } + + plan(4*3); + + // Initialize Admin connection + MYSQL* proxysql_admin = mysql_init(NULL); + if (!proxysql_admin) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + // Connnect to ProxySQL Admin + if (!mysql_real_connect(proxysql_admin, cl.host, cl.admin_username, cl.admin_password, NULL, cl.admin_port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin)); + return -1; + } + + // Initialize ProxySQL connection + MYSQL* proxysql = mysql_init(NULL); + if (!proxysql) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql)); + return -1; + } + + // Connect to ProxySQL + if (!mysql_real_connect(proxysql, cl.host, cl.username, cl.password, NULL, cl.port, NULL, 0)) { + fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql)); + return exit_status(); + } + + bool result; + unsigned int errcode; + unsigned int query_exec_count; + + diag("## Pre-test Check ##\n"); + diag("Executing query... [Reader HG]\n"); + MYSQL_QUERY__(proxysql, "SELECT 1"); + MYSQL_CLEAR_RESULT(proxysql); + diag("Executing query... [Writer HG]\n"); + MYSQL_QUERY__(proxysql, "DO 1"); + MYSQL_CLEAR_RESULT(proxysql); + diag("## Done\n"); + + diag("## Starting test ##\n"); + diag("Setting max_num_online_servers=1 in hostgroup: 1...\n"); + MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_hostgroup_attributes WHERE hostgroup_id=1"); + MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_hostgroup_attributes (hostgroup_id, max_num_online_servers) values (1,1)"); + MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + diag("Done\n"); + + diag("Executing query... [Reader HG]\n"); + std::tie(result, errcode, query_exec_count) = execute_query_and_check_result(proxysql, "SELECT 1", true, false, NUM_QUERY_EXEC); + ok(result, "Query execution should fail"); + ok(errcode == 9001, "Error code should be '9001'. Actual value:'%u'", errcode); + ok(query_exec_count == NUM_QUERY_EXEC, "Query execution count should be '%u'. Actual value:'%u'", NUM_QUERY_EXEC, query_exec_count); + + diag("Executing query... [Writer HG]\n"); + std::tie(result, errcode, query_exec_count) = execute_query_and_check_result(proxysql, "DO 1", false, true, NUM_QUERY_EXEC); + ok(result, "Query execution should succeed"); + ok(errcode == 0, "Error code should be '0'. Actual value:'%u'", errcode); + ok(query_exec_count == NUM_QUERY_EXEC, "Query execution count should be '%u'. Actual value:'%u'", NUM_QUERY_EXEC, query_exec_count); + + diag("Setting max_num_online_servers=100 in hostgroup: 1...\n"); + MYSQL_QUERY__(proxysql_admin, "UPDATE mysql_hostgroup_attributes SET max_num_online_servers=100 WHERE hostgroup_id=1"); + MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + diag("Done\n"); + + diag("Executing query... [Reader HG]...\n"); + std::tie(result, errcode, query_exec_count) = execute_query_and_check_result(proxysql, "SELECT 1", true, true, NUM_QUERY_EXEC); + ok(result, "Query execution should succeed"); + ok(errcode == 0, "Error code should be '0'. Actual value:'%u'", errcode); + ok(query_exec_count == NUM_QUERY_EXEC, "Query execution count should be '%u'. Actual value:'%u'", NUM_QUERY_EXEC, query_exec_count); + + diag("Executing query... [Writer HG]\n"); + std::tie(result, errcode, query_exec_count) = execute_query_and_check_result(proxysql, "DO 1", false, true, NUM_QUERY_EXEC); + ok(result, "Query execution should succeed"); + ok(errcode == 0, "Error code should be '0'. Actual value:'%u'", errcode); + ok(query_exec_count == NUM_QUERY_EXEC, "Query execution count should be '%u'. Actual value:'%u'", NUM_QUERY_EXEC, query_exec_count); + diag("## Done\n"); + +cleanup: + MYSQL_QUERY(proxysql_admin, "DELETE FROM mysql_hostgroup_attributes WHERE hostgroup_id=1"); + MYSQL_QUERY(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME"); + + mysql_close(proxysql); + mysql_close(proxysql_admin); + + return exit_status(); +} diff --git a/test/tap/tests/test_mysql_connect_retries-t.cpp b/test/tap/tests/test_mysql_connect_retries-t.cpp index 54dbcc8656..386400318b 100644 --- a/test/tap/tests/test_mysql_connect_retries-t.cpp +++ b/test/tap/tests/test_mysql_connect_retries-t.cpp @@ -15,11 +15,10 @@ * established. This is for regular and 'fast_forward' connections. */ +#include #include -#include #include #include -#include #include #include diff --git a/test/tap/tests/test_mysql_connect_retries_delay-t.cpp b/test/tap/tests/test_mysql_connect_retries_delay-t.cpp index 014c2eff6b..87f3cb00a9 100644 --- a/test/tap/tests/test_mysql_connect_retries_delay-t.cpp +++ b/test/tap/tests/test_mysql_connect_retries_delay-t.cpp @@ -9,17 +9,15 @@ * 5. Repeat the previous 3 points for several values. */ -#include #include +#include #include #include #include #include #include -#include #include "mysql.h" -#include "mysqld_error.h" #include "tap.h" #include "command_line.h" diff --git a/test/tap/tests/test_mysql_query_digests_stages-t.cpp b/test/tap/tests/test_mysql_query_digests_stages-t.cpp index c4c79ddf0d..86c208f56e 100644 --- a/test/tap/tests/test_mysql_query_digests_stages-t.cpp +++ b/test/tap/tests/test_mysql_query_digests_stages-t.cpp @@ -25,20 +25,18 @@ * * By default all types of tests are executed. */ -#include -#include -#include #include -#include #include +#include #include -#include +#include +#include +#include #include #include "json.hpp" #include "proxysql.h" #include "proxysql_utils.h" -#include "utils.h" #include "command_line.h" #include "tap.h" @@ -89,11 +87,15 @@ uint64_t benchmark_parsing(const vector& queries, int mode, uint32_t ite char* c_res = NULL; if (mode == 0) { + diag("Invalid test. mysql_query_digest_and_first_comment() was deprecated in 2.4.0"); + exit(EXIT_FAILURE); +/* c_res = mysql_query_digest_and_first_comment( const_cast(query.c_str()), query.length(), &first_comment, ((query.size() < QUERY_DIGEST_BUF) ? buf : NULL) ); +*/ } else if (mode == 1) { c_res = mysql_query_digest_and_first_comment_one_it( diff --git a/test/tap/tests/test_query_rules_fast_routing_algorithm-t.cpp b/test/tap/tests/test_query_rules_fast_routing_algorithm-t.cpp index 81b8c39ee4..ce0faa6049 100644 --- a/test/tap/tests/test_query_rules_fast_routing_algorithm-t.cpp +++ b/test/tap/tests/test_query_rules_fast_routing_algorithm-t.cpp @@ -215,6 +215,9 @@ int test_fast_routing_algorithm( MYSQL_QUERY_T(admin, "SET admin-debug_output=3"); MYSQL_QUERY_T(admin, "LOAD ADMIN VARIABLES TO RUNTIME"); MYSQL_QUERY_T(admin, "UPDATE debug_levels SET verbosity=7 WHERE module='debug_mysql_query_processor'"); + // If there is a generic debug filter (line==0) on Query_Processor.cpp , process_mysql_query() , disable it. + // If the filter was present it will be automatically recreated by the tester. + MYSQL_QUERY_T(admin, "DELETE FROM debug_filters WHERE filename='Query_Processor.cpp' AND line=0 AND funct='process_mysql_query'"); MYSQL_QUERY_T(admin, "LOAD DEBUG TO RUNTIME"); // Open the SQLite3 db for debugging diff --git a/test/tap/tests/test_sqlite3_pass_exts-t.cpp b/test/tap/tests/test_sqlite3_pass_exts-t.cpp index 352866c430..7bdd69b7c3 100644 --- a/test/tap/tests/test_sqlite3_pass_exts-t.cpp +++ b/test/tap/tests/test_sqlite3_pass_exts-t.cpp @@ -12,6 +12,7 @@ */ #include +#include #include #include #include diff --git a/test/tap/tests/test_tokenizer-t.cpp b/test/tap/tests/test_tokenizer-t.cpp deleted file mode 100644 index a409a55ddb..0000000000 --- a/test/tap/tests/test_tokenizer-t.cpp +++ /dev/null @@ -1,474 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "proxysql.h" - -#include "tap.h" -#include "command_line.h" - -#include "utils.h" - -#include - -__thread int mysql_thread___query_digests_max_query_length = 65000; -__thread bool mysql_thread___query_digests_lowercase = false; -__thread bool mysql_thread___query_digests_replace_null = false; -__thread bool mysql_thread___query_digests_no_digits = false; -__thread bool mysql_thread___query_digests_keep_comment = false; -__thread int mysql_thread___query_digests_grouping_limit = 3; -__thread int mysql_thread___query_digests_groups_grouping_limit = 1; - -using std::vector; -using std::pair; -using std::string; -using std::tuple; - -const vector> query_digest_pairs { - // TODO: KnownIssue - 10 - // { - // "select /* COMMENT */ 1", - // "select ?" - // // Actual: "select ?# final_comment" - // }, - { - "select/* COMMENT */ 1", - "select ?" - }, - { - "select/* COMMENT */1", - "select ?" - }, - // initial '#' comments - { - "# random_comment \n select 1.1", - "select ?" - }, - { - "#random_comment \nselect 1.1", - "select ?" - }, - { - "#random_comment\nselect 1.1", - "select ?" - }, - // initial '--' comments - { - "-- random_comment \n select 1.1", - "select ?" - }, - { - "--random_comment \nselect 1.1", - "select ?" - }, - { - "--random_comment\nselect 1.1", - "select ?" - }, - // final/initial '#|--' comments - { - "# random_comment\n select 1.1 #final_comment\n ", - "select ?" - }, - // TODO: KnownIssue - 1 - // { - // "# random_comment\n select 1.1# final_comment \n", - // "select ?" - // // Actual: "select ?# final_comment" - // }, - { - "# random_comment\n select 1.1 #final_comment \n ", - "select ?" - }, - { - "-- random_comment\n select 1.1 --final_comment\n ", - "select ?" - }, - { - "-- random_comment\n select 1.1-- final_comment \n", - "select ?" - }, - // NOTE: Comments with '--' should always be followed by an space. - // { - // "-- random_comment\n select 1.1--final_comment \n", - // "select ?" - // }, - { - "-- random_comment\n select 1.1 --final_comment \n ", - "select ?" - }, - // floats - { "select 1.1", "select ?" }, - { "select 99.1929", "select ?" }, - // exponentials - { "select 1.1e9", "select ?" }, - { "select 1.1e+9", "select ?" }, - { "select 1.1e-9", "select ?" }, - // TODO: KnownIssue - 2: Exponentials are case sensitive - // { "select 1.1E9", "select ?" }, - // { "select 1.1E+9", "select ?" }, - // { "select 1.1E-9", "select ?" }, - // operators - { "select 1 +1", "select ?+?" }, - { "select 1+ 1", "select ?+?" }, - { "select 1- 1", "select ?-?" }, - { "select 1 -1", "select ?-?" }, - { "select 1* 1", "select ?*?" }, - { "select 1 *1", "select ?*?" }, - { "select 1/ 1", "select ?/?" }, - { "select 1 /1", "select ?/?" }, - { "select 1% 1", "select ?%?" }, - { "select 1 %1", "select ?%?" }, - // operators and commas - { - "select 1+ 1, 1 -1, 1 * 1 , 1/1 , 100 % 3", - "select ?+?,?-?,?*?,?/?,?%?", - }, - { - "SELECT * FROM t t1, t t2 ,t t3,t t4 LIMIT 0", - "SELECT * FROM t t1,t t2,t t3,t t4 LIMIT ?" - }, - // mixing operators, commas and literals - { - "select 1+ 1,'1 -1', 1 * 1 , '1/1 ',100 % 3", - "select ?+?,?,?*?,?,?%?" - }, - { - "select 1+ 1 ,'1 -1' ,1 * 1 , '1 ' , 100 % 3", - "select ?+?,?,?*?,?,?%?" - }, - { - "select 1 + 1 , '1 - 1' , 1 * 1 , '1 ' , 100 % 3 ", - "select ?+?,?,?*?,?,?%?" - }, - // TODO: KnownIssue - 8: Operators not removed when extra space precedes the value - { - "select + 1", - "select +?" - }, - // strings - simple - { - "select * from t where t = \"foo\"", - "select * from t where t = ?", - }, - { - "select \"1+ 1, 1 -1, 1 * 1 , 1/1 , 100 % 3\"", - "select ?", - }, - // string - preceded by signs - outside parenthesis, not preceded by commas - { "select -\"1\"", "select -?", }, - { "select +\"1\",'foo'", "select +?,?", }, - // string - preceded by signs - inside parenthesis, or preceded by commas - { "select (-'89')", "select (?)", }, - { "select 10,-'89'", "select ?,?", }, - { "select 10, -'89' ", "select ?,?", }, - // string - leading strings get it's extra spaces removed, but not firsts - { "select '10', -'89' ", "select ?,?", }, - { "select 10, -'89 ',+'5'", "select ?,?,?", }, - // TODO: KnownIssue - 7: Spaces not removed after parenthesis when literal strings are preceded by '+|-' - { "select CONCAT( -'89'+'10')", "select CONCAT( ?+?)", }, - // ^ preserved space - { "select CONCAT( -'89'+'10')", "select CONCAT( ?+?)", }, - { "select CONCAT( -'89' + '10' )", "select CONCAT( ?+?)", }, - // TODO: KnownIssue - 8: Operators not removed when extra space precedes the literal (value) - { "select CONCAT(- '89')", "select CONCAT(-?)", }, - // ^ preserved operator - - // not modified - { "select * fromt t", "select * fromt t" }, - // test query_digest reduction - { - "SELECT * FROM tablename WHERE id IN (1,2,3,4,5,6,7,8,9,10)", - "SELECT * FROM tablename WHERE id IN (?,?,?,...)" - }, - { - "SELECT * FROM tablename WHERE id IN (1,2,3,4)", - "SELECT * FROM tablename WHERE id IN (?,?,?,...)" - }, - // invalid request grouping - { - "SELECT * tablename where id IN (1,2,3,4,5,6,7,8, AND j in (1,2,3,4,5,6 and k=1", - "SELECT * tablename where id IN (?,?,?,...,AND j in (?,?,?,... and k=?" - }, - // random insert queries - { - "INSERT INTO db.table(col1) VALUES ('val')", - "INSERT INTO db.table(col1) VALUES (?)" - }, - { - "INSERT INTO db.table (col1) VALUES ('val')", - "INSERT INTO db.table (col1) VALUES (?)" - }, - { - "INSERT INTO db.table( col1) VALUES ( 'val' )", - "INSERT INTO db.table( col1) VALUES (?)" - }, - { - "INSERT INTO db.table( col1) VALUES ( 'val' )", - "INSERT INTO db.table( col1) VALUES (?)" - }, - // TODO: KnownIssue - 6: When 'no-digits' is enabled, space before parenthesis closing brakcet is - // collapsed when an identifier name finish with a number. - // { - // "INSERT INTO db.table ( col1 ) VALUES ( 'val' )", - // "INSERT INTO db.table ( col1 ) VALUES (?)" - // }, - { - "INSERT INTO db.table (col1, col2,col3,col4) VALUES ('val',2,3,'foo')", - "INSERT INTO db.table (col1,col2,col3,col4) VALUES (?,?,?,...)" - }, - // TODO: KnownIssue - 6: When 'no-digits' is enabled, space before parenthesis closing brakcet is - // collapsed when an identifier name finish with a number. - // { - // "INSERT INTO db.table ( col1, col2,col3,col4 ) VALUES ('val',2,3,'foo')", - // "INSERT INTO db.table ( col1,col2,col3,col4 ) VALUES (?,?,?,...)" - // }, - { - "INSERT INTO db.table_25 (col1, col2,col3,col4) VALUES ('val',2,3,'foo')", - "INSERT INTO db.table_25 (col1,col2,col3,col4) VALUES (?,?,?,...)" - }, - { - "INSERT INTO db.table1_25 ( col_121,col2121 ,col12_3, col41203_ ) VALUES (?,?,?,...)", - "INSERT INTO db.table1_25 ( col_121,col2121,col12_3,col41203_ ) VALUES (?,?,?,...)" - }, - // TODO: KnownIssue - 5: Arithmetics operators breaks grouping - // { - // "INSERT INTO db.table ( col1, col2,col3,col4, col5 ) VALUES ('val',2,3,'foo', 5 + 10, 6 - 9)", - // "INSERT INTO db.table ( col1,col2,col3,col4,col5 ) VALUES (?,?,?,...)" - // // Actual: "INSERT INTO db.table ( col1,col2,col3,col4,col5 ) VALUES (?,?,?,... + -)" - // }, -}; - -const vector> queries_digests_grouping { - // test query_digest reduction - { - "SELECT * FROM tablename WHERE id IN (1,2, 3,4 ,5 ,6,7,8,9,10)", - "SELECT * FROM tablename WHERE id IN (?,...)" - }, - // invalid request grouping - { - "SELECT * tablename where id IN (1,2,3,4,5 , 6,7,8, AND j in (1, 2,3, 4 ,5,6,7,8,9 and k=1", - "SELECT * tablename where id IN (?,...,AND j in (?,... and k=?" - }, - // more random grouping - { - "SELECT (1.1, 1, 2, 13, 4.81, 12) FROM db.table", - "SELECT (?,...) FROM db.table" - }, - { - "SELECT (1.1, 1.12934 , 21.32 , 91, 91, 12.93 ) FROM db.table2", - "SELECT (?,...) FROM db.table2" - }, - { - "SELECT (1.1, 1.12934 , 21.32 , 91.2 , 91, 12 ) FROM db.table7", - "SELECT (?,...) FROM db.table7" - }, - { - "SELECT (1.1, 1.12934, 21.32, 391,2381,28.493,1283 ) FROM db.table2", - "SELECT (?,...) FROM db.table2" - }, - { - "SELECT (1.1, 1.12934, 21.32 , 91, 91, 12.1 ) FROM db.table3", - "SELECT (?,...) FROM db.table3" - } -}; - -const vector> null_queries_digests { - { - "select Null , '1*2/2',NULL,null , '1 ' , 100 % 3 ", - "select Null,?,NULL,null,?,?%?", - "select ?,?,?,?,?,?%?" - }, - // TODO: KnownIssue - 3: Grouping count isn't reset by NULL. - // { - // "INSERT INTO db.table VALUES ( Null , NULL, '',NULL, 'a', 'b', 'z',nuLL)", - // "INSERT INTO db.table VALUES (Null,NULL,?,NULL,?,?,?,nuLL)", - // "INSERT INTO db.table VALUES (?,?,?,...)" - // // Act: INSERT INTO db.table VALUES ( Null,NULL,?,NULL,?,?,...,nuLL) - // }, - { - "INSERT INTO db.table VALUES ( NULL, 'a', 'b', 'z', -4, nuLL)", - // TODO: KnownIssue - 4: Spaces preceding NULL values are not properly removed - "INSERT INTO db.table VALUES ( NULL,?,?,?,...,nuLL)", - "INSERT INTO db.table VALUES (?,?,?,...)" - }, -}; - -std::string replace_str(const std::string& str, const std::string& match, const std::string& repl) { - if(match.empty()) { - return str; - } - - std::string result = str; - size_t start_pos = 0; - - while((start_pos = result.find(match, start_pos)) != std::string::npos) { - result.replace(start_pos, match.length(), repl); - start_pos += repl.length(); - } - - return result; -} - -std::string increase_mark_num(const std::string query, uint32_t num) { - std::string result = query; - std::string marks = ""; - - for (uint32_t i = 0; i < num - 1; i++) { - marks += "?,"; - } - marks += "?,..."; - - result = replace_str(result, "?,...", marks); - - return result; -} - -char is_digit_char(char c) { - if(c >= '0' && c <= '9') { - return 1; - } - return 0; -} - -vector extract_numbers(const string query) { - vector numbers {}; - string number {}; - - for (const char c : query) { - if (is_digit_char(c)) { - number += c; - } else { - if (!number.empty()) { - numbers.push_back(number); - number.clear(); - } - } - } - - return numbers; -} - -string replace_numbers(const string query, const char mark) { - vector numbers { extract_numbers(query) }; - std::sort( - numbers.begin(), numbers.end(), - [](const string& s1, const string& s2) -> bool { return s1.size() > s2.size(); } - ); - - string query_res { query }; - - for (const string& num : numbers) { - query_res = replace_str(query_res, num, string { mark }); - } - - return query_res; -} - -int main(int argc, char** argv) { - if (query_digest_pairs.size() != query_digest_pairs.size()) { - ok(0, "queries and exp_results sizes doesn't match"); - return exit_status(); - } - - plan( - query_digest_pairs.size()*2 + queries_digests_grouping.size()*5 + null_queries_digests.size()*2 - ); - - char buf[QUERY_DIGEST_BUF]; - - const auto test_query_digests = [&](bool replace_digits) -> void { - mysql_thread___query_digests_no_digits=replace_digits; - - for (size_t i = 0; i < query_digest_pairs.size(); i++) { - const auto& query = query_digest_pairs[i].first; - const auto& query_str_rep = replace_str(query_digest_pairs[i].first, "\n", "\\n"); - char* first_comment = NULL; - std::string exp_res {}; - - if (replace_digits == false) { - exp_res = query_digest_pairs[i].second; - } else { - exp_res = replace_numbers(query_digest_pairs[i].second, '?'); - } - - char* c_res = mysql_query_digest_and_first_comment(const_cast(query.c_str()), query.length(), &first_comment, buf); - const std::string result(c_res); - std::string ok_msg {}; - - if (replace_digits == false) { - ok_msg = "Digest should be equal to exp result:\n * Query: `%s`,\n * Act: `%s`,\n * Exp: `%s`"; - } else { - ok_msg = "No-Digits digest should be equal to exp result:\n * Query: `%s`,\n * Act: `%s`,\n * Exp: `%s`"; - } - - ok( - result == exp_res, ok_msg.c_str(), - query_str_rep.c_str(), result.c_str(), exp_res.c_str() - ); - } - - mysql_thread___query_digests_no_digits=0; - }; - - /* Test queries without replacing digits */ - test_query_digests(false); - /* Test queries replacing digits */ - test_query_digests(true); - - const auto test_null_replacting = [&](bool replace_nulls) -> void { - mysql_thread___query_digests_replace_null=replace_nulls; - - for (size_t i = 0; i < null_queries_digests.size(); i++) { - const auto& query = std::get<0>(null_queries_digests[i]); - std::string exp_res {}; - - if (replace_nulls) { - exp_res = std::get<2>(null_queries_digests[i]); - } else { - exp_res = std::get<1>(null_queries_digests[i]); - } - - char* c_res = mysql_query_digest_and_first_comment(const_cast(query.c_str()), query.length(), NULL, buf); - std::string result(c_res); - - ok( - result == exp_res, - "Replaced NULL values digest should be equal to exp result:" - "\n * Query: `%s`,\n * Act: `%s`,\n * Exp: `%s`", - query.c_str(), result.c_str(), exp_res.c_str() - ); - } - - mysql_thread___query_digests_replace_null=0; - }; - - /* Test queries containing 'NULL', NOT replacing the 'NULL' values */ - test_null_replacting(false); - /* Test queries containing 'NULL', replacing the 'NULL' values */ - test_null_replacting(true); - - for (size_t i = 0; i < queries_digests_grouping.size(); i++) { - for (int j = 1; j <= 5; j++) { - mysql_thread___query_digests_grouping_limit = j; - - const auto& query = queries_digests_grouping[i].first; - const auto& exp_res = increase_mark_num(queries_digests_grouping[i].second, j); - - char* c_res = mysql_query_digest_and_first_comment(const_cast(query.c_str()), query.length(), NULL, buf); - std::string result(c_res); - - ok( - result == exp_res, - "Grouping digest should be equal to exp result:" - "\n * Query: `%s`,\n * Act: `%s`,\n * Exp: `%s`", - query.c_str(), result.c_str(), exp_res.c_str() - ); - } - } - - return exit_status(); -} diff --git a/test/tap/tests_with_deps/deprecate_eof_support/Makefile b/test/tap/tests_with_deps/deprecate_eof_support/Makefile index 0c7948a8c2..36b52fd9f8 100644 --- a/test/tap/tests_with_deps/deprecate_eof_support/Makefile +++ b/test/tap/tests_with_deps/deprecate_eof_support/Makefile @@ -113,8 +113,12 @@ endif OPT := $(STDCPP) -O2 -ggdb -Wl,--no-as-needed -Wl,-rpath,"../../tap" $(WGCOV) $(WASAN) -IDIRS := -I$(TAP_IDIR) -I$(RE2_IDIR) -I$(PROXYSQL_IDIR) -I$(JEMALLOC_IDIR) -I$(LIBCONFIG_IDIR) -I$(MICROHTTPD_IDIR) -I$(LIBHTTPSERVER_IDIR) -I$(CURL_IDIR) -I$(EV_IDIR) -I$(PROMETHEUS_IDIR) -I$(DOTENV_DYN_IDIR) -I$(SSL_IDIR) -I$(SQLITE3_IDIR) -I$(JSON_IDIR) -LDIRS := -L$(TAP_LDIR) -L$(RE2_LDIR) -L$(PROXYSQL_LDIR) -L$(JEMALLOC_LDIR) -L$(LIBCONFIG_LDIR) -L$(MICROHTTPD_LDIR) -L$(LIBHTTPSERVER_LDIR) -L$(CURL_LDIR) -L$(EV_LDIR) -L$(PROMETHEUS_LDIR) -L$(DOTENV_DYN_LDIR) -L$(SSL_LDIR) -L$(SQLITE3_LDIR) -L$(PCRE_LDIR) +IDIRS := -I$(TAP_IDIR) -I$(RE2_IDIR) -I$(PROXYSQL_IDIR) -I$(JEMALLOC_IDIR) -I$(LIBCONFIG_IDIR)\ + -I$(MICROHTTPD_IDIR) -I$(LIBHTTPSERVER_IDIR) -I$(CURL_IDIR) -I$(EV_IDIR) -I$(PROMETHEUS_IDIR)\ + -I$(DOTENV_DYN_IDIR) -I$(SSL_IDIR) -I$(SQLITE3_IDIR) -I$(JSON_IDIR) +LDIRS := -L$(TAP_LDIR) -L$(RE2_LDIR) -L$(PROXYSQL_LDIR) -L$(JEMALLOC_LDIR) -L$(LIBCONFIG_LDIR)\ + -L$(MICROHTTPD_LDIR) -L$(LIBHTTPSERVER_LDIR) -L$(CURL_LDIR) -L$(EV_LDIR) -L$(PROMETHEUS_LDIR)\ + -L$(DOTENV_DYN_LDIR) -L$(SSL_LDIR) -L$(SQLITE3_LDIR) -L$(PCRE_LDIR) ### main targets @@ -151,7 +155,7 @@ $(TEST_MYSQL_LDIR)/libmysqlclient.a: #tests: build_test_deps tests: $(patsubst %.cpp,%,$(wildcard *-t.cpp)) ok_packet_mixed_queries-t fwd_eof_query fwd_eof_ok_query -COMMONARGS = $(OPT) -Wl,-Bdynamic -ltap -lcpp_dotenv -lcurl -lssl -lcrypto -lz -ldl -lpthread -DGITVERSION=\"$(GIT_VERSION)\" +COMMONARGS = $(OPT) -Wl,-Bdynamic -ltap -lcpp_dotenv -lcurl -lre2 -lssl -lcrypto -lz -ldl -lpthread -DGITVERSION=\"$(GIT_VERSION)\" ok_packet_mixed_queries-t: eof_packet_mixed_queries-t.cpp $(CXX) $< $(IDIRS) $(LDIRS) -I$(MARIADB_IDIR) -L$(MARIADB_LDIR) -lmariadbclient $(COMMONARGS) -o $@ diff --git a/test/tap/tests_with_deps/deprecate_eof_support/deprecate_eof_cache-t.cpp b/test/tap/tests_with_deps/deprecate_eof_support/deprecate_eof_cache-t.cpp index e46e2d3f94..51c68ade70 100644 --- a/test/tap/tests_with_deps/deprecate_eof_support/deprecate_eof_cache-t.cpp +++ b/test/tap/tests_with_deps/deprecate_eof_support/deprecate_eof_cache-t.cpp @@ -1,11 +1,8 @@ -#include #include #include #include #include #include -#include -#include #include "mysql.h" #include "mysqld_error.h" diff --git a/test/tap/tests_with_deps/deprecate_eof_support/eof_fast_forward-t.cpp b/test/tap/tests_with_deps/deprecate_eof_support/eof_fast_forward-t.cpp index 3a50ce1d80..82b5cf14dc 100644 --- a/test/tap/tests_with_deps/deprecate_eof_support/eof_fast_forward-t.cpp +++ b/test/tap/tests_with_deps/deprecate_eof_support/eof_fast_forward-t.cpp @@ -18,7 +18,6 @@ #include #include "mysql.h" -#include "mysqld_error.h" #include "proxysql_utils.h" @@ -57,7 +56,6 @@ int create_testing_tables(MYSQL* mysql_server) { int perform_workload_on_connection(MYSQL* proxy, MYSQL* admin) { // Change default query rules to avoid replication issues -// MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET destination_hostgroup=0 WHERE rule_id=2"); MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET active=0"); MYSQL_QUERY(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); @@ -175,7 +173,6 @@ int perform_workload_on_connection(MYSQL* proxy, MYSQL* admin) { ok(ops_err_msg.empty() == true, "Operations should complete successfully - '%s'", ops_err_msg.c_str()); // Recover default query rules -// MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET destination_hostgroup=0 WHERE rule_id=2"); MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET active=1"); MYSQL_QUERY(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); diff --git a/test/tap/tests_with_deps/deprecate_eof_support/eof_mixed_flags_queries-t.cpp b/test/tap/tests_with_deps/deprecate_eof_support/eof_mixed_flags_queries-t.cpp index f6ce4b329c..556d77ba50 100644 --- a/test/tap/tests_with_deps/deprecate_eof_support/eof_mixed_flags_queries-t.cpp +++ b/test/tap/tests_with_deps/deprecate_eof_support/eof_mixed_flags_queries-t.cpp @@ -28,27 +28,6 @@ using std::string; using std::vector; -vector> get_all_bin_vec(size_t tg_size) { - vector> all_bin_strs {}; - vector bin_vec(tg_size, 0); - - for (size_t i = 0; i < tg_size; i++) { - if (i == 0) { - bin_vec[i] = 0; - for (const vector p : get_permutations(bin_vec)) { - all_bin_strs.push_back(p); - } - } - - bin_vec[i] = 1; - for (const vector p : get_permutations(bin_vec)) { - all_bin_strs.push_back(p); - } - } - - return all_bin_strs; -} - vector gen_all_configs(const string& ff_user) { vector> all_bin_vec { get_all_bin_vec(5) }; std::sort(all_bin_vec.begin(), all_bin_vec.end()); diff --git a/test/tap/tests_with_deps/deprecate_eof_support/eof_packet_mixed_queries-t.cpp b/test/tap/tests_with_deps/deprecate_eof_support/eof_packet_mixed_queries-t.cpp index d55fa7f2f6..bba026eba2 100644 --- a/test/tap/tests_with_deps/deprecate_eof_support/eof_packet_mixed_queries-t.cpp +++ b/test/tap/tests_with_deps/deprecate_eof_support/eof_packet_mixed_queries-t.cpp @@ -11,7 +11,6 @@ * - Fast forward */ -#include #include #include #include @@ -150,13 +149,14 @@ pair perform_stmt_select_query( p_binds[0].is_null = 0; p_binds[0].length = 0; - if (mysql_stmt_bind_param(stmts, p_binds)) { - string_format("'mysql_stmt_bind_param' at line %d failed: %s", err_msg, __LINE__, mysql_stmt_error(stmts)); + int myerr = 0; + + if ((myerr = mysql_stmt_bind_param(stmts, p_binds))) { + string_format("'mysql_stmt_bind_param' at line %d failed with error %d", err_msg, __LINE__, myerr); return { EXIT_FAILURE, err_msg }; } - int s_err = mysql_stmt_execute(stmts); - if (s_err != EXIT_SUCCESS) { + if ((myerr = mysql_stmt_execute(stmts))) { string_format("'mysql_stmt_execute' at line %d failed: %s", err_msg, __LINE__, mysql_stmt_error(stmts)); return { EXIT_FAILURE, err_msg }; } @@ -184,20 +184,18 @@ pair perform_stmt_select_query( r_binds[2].length= &length[2]; r_binds[2].error= &error[2]; - s_err = mysql_stmt_bind_result(stmts, r_binds); - if (s_err != EXIT_SUCCESS) { - string_format("'mysql_stmt_bind_result' at line %d failed: %s", err_msg, __LINE__, mysql_stmt_error(stmts)); + if ((myerr = mysql_stmt_bind_result(stmts, r_binds))) { + string_format("'mysql_stmt_bind_result' at line %d failed with error %d", err_msg, __LINE__, myerr); return { EXIT_FAILURE, err_msg }; } - if (mysql_stmt_store_result(stmts) || mysql_errno(mysql_server) != EXIT_SUCCESS) { - string_format("'mysql_stmt_store_result' at line %d failed: %s", err_msg, __LINE__, mysql_stmt_error(stmts)); + if ((myerr = mysql_stmt_store_result(stmts)) || mysql_errno(mysql_server) != EXIT_SUCCESS) { + string_format("'mysql_stmt_store_result' at line %d failed with error %d", err_msg, __LINE__, myerr); return { EXIT_FAILURE, err_msg }; } - s_err = mysql_stmt_fetch(stmts); - if (s_err != EXIT_SUCCESS) { - string_format("'mysql_stmt_fetch' at line %d failed: %s", err_msg, __LINE__, mysql_stmt_error(stmts)); + if ((myerr = mysql_stmt_fetch(stmts))) { + string_format("'mysql_stmt_fetch' at line %d failed with error %d", err_msg, __LINE__, myerr); return { EXIT_FAILURE, err_msg }; } @@ -313,8 +311,10 @@ pair perform_stmt_update_query( bindsu[2].is_null = 0; bindsu[2].length = 0; - if (mysql_stmt_bind_param(stmtu, bindsu)) { - string_format("'mysql_stmt_bind_param' at line %d failed: %s", err_msg, __LINE__, mysql_stmt_error(stmtu)); + int myerr = 0; + + if ((myerr=mysql_stmt_bind_param(stmtu, bindsu))) { + string_format("'mysql_stmt_bind_param' at line %d failed with error %d", err_msg, __LINE__, myerr); return { EXIT_FAILURE, err_msg }; } @@ -497,9 +497,6 @@ int test_target_queries(MYSQL* proxy) { return !(op_res.first == EXIT_SUCCESS); } -// NOTE: Test hardcoded to hostgroup '0' -const uint32_t HG_ID = 0; - int main(int argc, char** argv) { CommandLine cl; @@ -524,8 +521,8 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - // Change default query rules to avoid replication issues - MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET destination_hostgroup=0 WHERE rule_id=2"); + // Change default query rules to avoid replication issues; This test only requires the default hostgroup + MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET active=0"); MYSQL_QUERY(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); MYSQL* proxy = mysql_init(NULL); @@ -578,7 +575,7 @@ int main(int argc, char** argv) { cleanup: // Recover default query rules - MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET destination_hostgroup=0 WHERE rule_id=2"); + MYSQL_QUERY(admin, "UPDATE mysql_query_rules SET active=1"); MYSQL_QUERY(admin, "LOAD MYSQL QUERY RULES TO RUNTIME"); mysql_close(admin);