Repository: sysown/proxysql (language: cpp)

Issue:
Fix issues found with Cluster found during testing
- [x] A clear description of the issue

##### Deadlock

A deadlock was triggered under certain timing conditions during Cluster synchronization. Relevant logs:

- [deadlock_threads_backtrace.log](https://github.com/sysown/proxysql/files/8538479/deadlock_threads_backtrace.log)
- [proxysql.log.tar.gz](https://github.com/sysown/proxysql/files/8538595/proxysql.log.tar.gz)

##### ProxySQL servers sync

- ProxySQL servers failed to sync under certain conditions:

```
2022-04-18 08:43:10 ProxySQL_Cluster.cpp:1694:pull_proxysql_servers_from_peer(): [INFO] Cluster: Fetching ProxySQL Servers from peer 127.0.0.1:6032 started. Expected checksum: 0x0000000000000000
2022-04-18 08:43:10 ProxySQL_Cluster.cpp:1720:pull_proxysql_servers_from_peer(): [INFO] Cluster: Fetching ProxySQL Servers from peer 127.0.0.1:6032 completed. Computed checksum: 0x00
2022-04-18 08:43:10 ProxySQL_Cluster.cpp:1750:pull_proxysql_servers_from_peer(): [INFO] Cluster: Fetching ProxySQL Servers from peer 127.0.0.1:6032 failed: Checksum changed from 0x0000000000000000 to 0x00
```

- Invalid memory accesses.

- [x] ProxySQL version

v2.x

- [x] OS version

ArchLinux




Discussion / hints:

Fix patch:
diff --git a/include/proxysql_glovars.hpp b/include/proxysql_glovars.hpp
index ddd19eb2f2..4d7d0529df 100644
--- a/include/proxysql_glovars.hpp
+++ b/include/proxysql_glovars.hpp
@@ -14,6 +14,18 @@ namespace ez {
 class ezOptionParser;
 };
 
+/**
+ * @brief Helper function used to replace spaces and zeros by '0' char in the supplied checksum buffer.
+ * @param checksum Input buffer containing the checksum.
+ */
+inline void replace_checksum_zeros(char* checksum) {
+	for (int i=2; i<18; i++) {
+		if (checksum[i]==' ' || checksum[i]==0) {
+			checksum[i]='0';
+		}
+	}
+}
+
 class ProxySQL_Checksum_Value {
 	public:
 	char *checksum;
@@ -28,12 +40,7 @@ class ProxySQL_Checksum_Value {
 	void set_checksum(char *c) {
 		memset(checksum,0,20);
 		strncpy(checksum,c,18);
-		for (int i=2; i<18; i++) {
-			if (checksum[i]==' ' || checksum[i]==0) {
-				checksum[i]='0';
-			}
-		}
-
+		replace_checksum_zeros(checksum);
 	}
 	~ProxySQL_Checksum_Value() {
 		free(checksum);
diff --git a/include/proxysql_utils.h b/include/proxysql_utils.h
index 42dfac814f..4a5a89607f 100644
--- a/include/proxysql_utils.h
+++ b/include/proxysql_utils.h
@@ -4,6 +4,7 @@
 #include <type_traits>
 #include <memory>
 #include <string>
+#include <vector>
 
 template<class...> struct conjunction : std::true_type { };
 template<class B1> struct conjunction<B1> : B1 { };
@@ -40,4 +41,77 @@ int string_format(const std::string& str, std::string& result, Args... args) {
 	return err;
 }
 
-#endif
\ No newline at end of file
+/**
+ * @brief Simple struct that holds the 'timeout options' for 'wexecvp'.
+ */
+struct to_opts {
+	/**
+	 * @brief Timeout for the script execution to be completed, in case of being
+	 *   exceeded, the script will be terminated.
+	 */
+	uint timeout_us;
+	/**
+	 * @brief Timeout used for 'select()' non blocking calls.
+	 */
+	uint select_to_us;
+	/**
+	 * @brief The duration of the sleeps between the checks being performed
+	 *   on the child process waiting it to exit after being signaled to terminate,
+	 *   before issuing 'SIGKILL'.
+	 */
+	uint it_delay_us;
+	/**
+	 * @brief The timeout to be waited on the child process after being signaled
+	 *   with 'SIGTERM' before being forcely terminated by 'SIGKILL'.
+	 */
+	uint sigkill_timeout_us;
+};
+
+/**
+ * @brief Helper function to launch an executable in a child process through 'fork()' and
+ *   'execvp()' and retrieve their 'stderr' and 'stdout' to the caller toguether with
+ *   the result of it's execution.
+ *
+ * @param file The file to be executed.
+ * @param argv The arguments to be supplied to the file being executed.
+ * @param opts Struct holding timeout options to consider for the launched process.
+ * @param s_stdout Output string to hold the output 'stdout' of the child process.
+ * @param s_stderr Output string to hold the output 'stderr' of the child process.
+ *
+ * @return 0 In case of success or one of the following error codes:
+ *   - '-1' in case any 'pipe()' creation failed.
+ *   - '-2' in case 'fork()' call failed.
+ *   - '-3' in case 'fcntl()' call failed .
+ *   - '-4' in case of resource exhaustion, file descriptors being used exceeds 'FD_SETSIZE'.
+ *   - '-5' in case 'select()' call failed.
+ *   - '-6' in case 'read()' from pipes failed with a non-expected error.
+ *   - 'ETIME' in case the executable has exceeded the timeout supplied in 'opts.timeout_us'.
+ *  In all this cases 'errno' is set to the error reported by the failing 'system call'.
+ */
+int wexecvp(
+	const std::string& file,
+	const std::vector<const char*>& argv,
+	const to_opts* opts,
+	std::string& s_stdout,
+	std::string& s_stderr
+);
+
+/**
+ * @brief Returns the current timestamp in microseconds.
+ * @return The current timestamp in microseconds.
+ */
+uint64_t get_timestamp_us();
+
+/**
+ * @brief Helper function to replace all the occurrences in a string of a matching substring in favor
+ *   of another string.
+ *
+ * @param str The string which copy is going to be searched for matches to be replaced.
+ * @param match The substring to be matched inside the string.
+ * @param repl The string for which matches are going to be replaced.
+ *
+ * @return A string in which all the matches of 'match' within 'str' has been replaced by 'repl'.
+ */
+std::string replace_str(const std::string& str, const std::string& match, const std::string& repl);
+
+#endif
diff --git a/lib/Makefile b/lib/Makefile
index bb28b7ddf0..5f1fa6664a 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -123,7 +123,7 @@ MYCXXFLAGS=-std=c++11 $(MYCFLAGS) $(PSQLCH) $(ENABLE_EPOLL)
 default: libproxysql.a
 .PHONY: default
 
-_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
+_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
 OBJ_CXX = $(patsubst %,$(ODIR)/%,$(_OBJ_CXX))
 HEADERS = ../include/*.h ../include/*.hpp
 
diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp
index 547100f409..885781f1cb 100644
--- a/lib/ProxySQL_Admin.cpp
+++ b/lib/ProxySQL_Admin.cpp
@@ -6680,10 +6680,13 @@ void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db,
 		free(previous_default_charset);
 		free(previous_default_collation_connection);
 		GloMTH->commit();
+		GloMTH->wrunlock();
 		if (checksum_variables.checksum_mysql_variables) {
+			// 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, false);
+			flush_mysql_variables___runtime_to_database(admindb, false, false, false, true, true);
 			char *error=NULL;
 			int cols=0;
 			int affected_rows=0;
@@ -6710,7 +6713,6 @@ void ProxySQL_Admin::flush_mysql_variables___database_to_runtime(SQLite3DB *db,
 			pthread_mutex_unlock(&GloVars.checksum_mutex);
 			delete resultset;
 		}
-		GloMTH->wrunlock();
 	}
 	if (resultset) delete resultset;
 }
diff --git a/lib/ProxySQL_Cluster.cpp b/lib/ProxySQL_Cluster.cpp
index f54ba2cdde..b92bae492a 100644
--- a/lib/ProxySQL_Cluster.cpp
+++ b/lib/ProxySQL_Cluster.cpp
@@ -683,41 +683,6 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) {
 			}
 		}
 	}
-	if (diff_ps) {
-		v = &checksums_values.proxysql_servers;
-		unsigned long long own_version = __sync_fetch_and_add(&GloVars.checksums_values.proxysql_servers.version,0);
-		unsigned long long own_epoch = __sync_fetch_and_add(&GloVars.checksums_values.proxysql_servers.epoch,0);
-		char* own_checksum = __sync_fetch_and_add(&GloVars.checksums_values.proxysql_servers.checksum,0);
-		if (v->version > 1) {
-			if (
-				(own_version == 1) // we just booted
-				||
-				(v->epoch > own_epoch) // epoch is newer
-			) {
-				if (v->diff_check >= diff_ps) {
-					proxy_info("Cluster: detected a peer %s:%d with proxysql_servers version %llu, epoch %llu, diff_check %u. Own version: %llu, epoch: %llu. Proceeding with remote sync\n", hostname, port, v->version, v->epoch, v->diff_check, own_version, own_epoch);
-					// v->checksum will be destroyed when calling pull_proxysql_servers_from_peer()
-					// thus we need to copy it now
-					char *expected_checksum = strdup(v->checksum);
-					GloProxyCluster->pull_proxysql_servers_from_peer((const char *)expected_checksum);
-					if (strncmp(expected_checksum, GloVars.checksums_values.proxysql_servers.checksum, 20)==0) {
-						// we copied from the remote server, let's also copy the same epoch
-						GloVars.checksums_values.proxysql_servers.epoch = v->epoch;
-					}
-					free(expected_checksum);
-				}
-			}
-			if ((v->epoch == own_epoch) && v->diff_check && ((v->diff_check % (diff_ps*10)) == 0)) {
-				proxy_error("Cluster: detected a peer %s:%d with proxysql_servers version %llu, epoch %llu, diff_check %u, checksum %s. Own version: %llu, epoch: %llu, checksum %s. Sync conflict, epoch times are EQUAL, can't determine which server holds the latest config, we won't sync. This message will be repeated every %llu checks until LOAD MYSQL SERVERS TO RUNTIME is executed on candidate master.\n", hostname, port, v->version, v->epoch, v->diff_check, v->checksum, own_version, own_epoch, own_checksum, (diff_ps*10));
-				GloProxyCluster->metrics.p_counter_array[p_cluster_counter::sync_conflict_proxysql_servers_share_epoch]->Increment();
-			}
-		} else {
-			if (v->diff_check && (v->diff_check % (diff_ps*10)) == 0) {
-					proxy_warning("Cluster: detected a peer %s:%d with proxysql_servers version %llu, epoch %llu, diff_check %u. Own version: %llu, epoch: %llu. diff_check is increasing, but version 1 doesn't allow sync. This message will be repeated every %llu checks until LOAD PROXYSQL SERVERS TO RUNTIME is executed on candidate master.\n", hostname, port, v->version, v->epoch, v->diff_check, own_version, own_epoch, (diff_ps*10));
-				GloProxyCluster->metrics.p_counter_array[p_cluster_counter::sync_delayed_proxysql_servers_version_one]->Increment();
-			}
-		}
-	}
 	if (diff_mv) {
 		v = &checksums_values.mysql_variables;
 		unsigned long long own_version = __sync_fetch_and_add(&GloVars.checksums_values.mysql_variables.version, 0);
@@ -814,6 +779,48 @@ void ProxySQL_Node_Entry::set_checksums(MYSQL_RES *_r) {
 			}
 		}
 	}
+	// IMPORTANT-NOTE: This action should ALWAYS be performed the last, since the 'checksums_values' gets
+	// invalidated by 'pull_proxysql_servers_from_peer' and further memory accesses would be invalid.
+	if (diff_ps) {
+		v = &checksums_values.proxysql_servers;
+		unsigned long long own_version = __sync_fetch_and_add(&GloVars.checksums_values.proxysql_servers.version,0);
+		unsigned long long own_epoch = __sync_fetch_and_add(&GloVars.checksums_values.proxysql_servers.epoch,0);
+		char* own_checksum = __sync_fetch_and_add(&GloVars.checksums_values.proxysql_servers.checksum,0);
+		if (v->version > 1) {
+			// NOTE: Backup values: 'v' gets invalidated by 'pull_proxysql_servers_from_peer()'
+			unsigned long long v_epoch = v->epoch;
+			unsigned long long v_version = v->version;
+			unsigned int v_diff_check = v->diff_check;
+			char* v_exp_checksum = strdup(v->checksum);
+
+			if (
+				(own_version == 1) // we just booted
+				||
+				(v->epoch > own_epoch) // epoch is newer
+			) {
+				if (v->diff_check >= diff_ps) {
+					proxy_info("Cluster: detected a peer %s:%d with proxysql_servers version %llu, epoch %llu, diff_check %u. Own version: %llu, epoch: %llu. Proceeding with remote sync\n", hostname, port, v->version, v->epoch, v->diff_check, own_version, own_epoch);
+					// thus we need to copy it now
+					GloProxyCluster->pull_proxysql_servers_from_peer((const char *)v_exp_checksum);
+					if (strncmp(v_exp_checksum, GloVars.checksums_values.proxysql_servers.checksum, 20)==0) {
+						// we copied from the remote server, let's also copy the same epoch
+						GloVars.checksums_values.proxysql_servers.epoch = v_epoch;
+					}
+				}
+			}
+			if ((v_epoch == own_epoch) && v_diff_check && ((v_diff_check % (diff_ps*10)) == 0)) {
+				proxy_error("Cluster: detected a peer %s:%d with proxysql_servers version %llu, epoch %llu, diff_check %u, checksum %s. Own version: %llu, epoch: %llu, checksum %s. Sync conflict, epoch times are EQUAL, can't determine which server holds the latest config, we won't sync. This message will be repeated every %llu checks until LOAD MYSQL SERVERS TO RUNTIME is executed on candidate master.\n", hostname, port, v_version, v_epoch, v_diff_check, v_exp_checksum, own_version, own_epoch, own_checksum, (diff_ps*10));
+				GloProxyCluster->metrics.p_counter_array[p_cluster_counter::sync_conflict_proxysql_servers_share_epoch]->Increment();
+			}
+
+			free(v_exp_checksum);
+		} else {
+			if (v->diff_check && (v->diff_check % (diff_ps*10)) == 0) {
+					proxy_warning("Cluster: detected a peer %s:%d with proxysql_servers version %llu, epoch %llu, diff_check %u. Own version: %llu, epoch: %llu. diff_check is increasing, but version 1 doesn't allow sync. This message will be repeated every %llu checks until LOAD PROXYSQL SERVERS TO RUNTIME is executed on candidate master.\n", hostname, port, v->version, v->epoch, v->diff_check, own_version, own_epoch, (diff_ps*10));
+				GloProxyCluster->metrics.p_counter_array[p_cluster_counter::sync_delayed_proxysql_servers_version_one]->Increment();
+			}
+		}
+	}
 }
 
 void ProxySQL_Cluster::pull_mysql_query_rules_from_peer() {
@@ -1713,9 +1720,10 @@ void ProxySQL_Cluster::pull_proxysql_servers_from_peer(const char *expected_chec
 					free(pta);
 					uint64_t hash1 = result3->raw_checksum();
 					uint32_t d32[2];
-					char buf[20];
+					char buf[20] = { 0 };
 					memcpy(&d32, &hash1, sizeof(hash1));
 					sprintf(buf,"0x%0X%0X", d32[0], d32[1]);
+					replace_checksum_zeros(buf);
 					delete result3;
 					proxy_info("Cluster: Fetching ProxySQL Servers from peer %s:%d completed. Computed checksum: %s\n", hostname, port, buf);
 					if (strcmp(buf, expected_checksum)==0) {
@@ -1737,6 +1745,16 @@ void ProxySQL_Cluster::pull_proxysql_servers_from_peer(const char *expected_chec
 							GloAdmin->admindb->execute(query);
 							free(query);
 						}
+
+						proxy_info("Dumping fetched 'proxysql_servers'\n");
+						char *error = NULL;
+						int cols = 0;
+						int affected_rows = 0;
+						SQLite3_result *resultset = NULL;
+						GloAdmin->admindb->execute_statement((char *)"SELECT * FROM proxysql_servers", &error, &cols, &affected_rows, &resultset);
+						resultset->dump_to_stderr();
+						delete resultset;
+
 						proxy_info("Cluster: Loading to runtime ProxySQL Servers from peer %s:%d\n", hostname, port);
 						GloAdmin->load_proxysql_servers_to_runtime(false);
 						if (GloProxyCluster->cluster_proxysql_servers_save_to_disk == true) {
diff --git a/lib/ProxySQL_RESTAPI_Server.cpp b/lib/ProxySQL_RESTAPI_Server.cpp
index 518c5e6f4e..974d5d62ab 100644
--- a/lib/ProxySQL_RESTAPI_Server.cpp
+++ b/lib/ProxySQL_RESTAPI_Server.cpp
@@ -3,12 +3,11 @@
 #include "httpserver.hpp"
 
 #include <functional>
-#include <fcntl.h>
 #include <sstream>
-#include <sys/time.h>
-
 
 #include "ProxySQL_RESTAPI_Server.hpp"
+#include "proxysql_utils.h"
+
 using namespace httpserver;
 
 
@@ -21,386 +20,6 @@ using namespace httpserver;
 
 extern ProxySQL_Admin *GloAdmin;
 
-/**
- * @brief Kills the given process sending an initial 'SIGTERM' to it. If the process doesn't
- *   respond within 'timeout_us' to the initial signal a 'SIGKILL' is issued to it.
- *
- * @param child_pid The process pid to be terminated.
- * @param timeout_us The timeout to be waited after sending the initial 'SIGTERM' before
- *   signaling the process with SIGKILL.
- * @param it_sleep_us The microseconds to sleep while waiting for the process to terminate.
- *
- * @return The error returned by 'kill' in case of it failing.
- */
-int kill_child_proc(pid_t child_pid, const uint timeout_us, const uint it_sleep_us) {
-	uint err = 0;
-	uint waited = 0;
-	int child_status = 0;
-
-	err = kill(child_pid, SIGTERM);
-
-	while (waitpid(child_pid, &child_status, WNOHANG) == 0) {
-		if (waited >= timeout_us) {
-			kill(child_pid, SIGKILL);
-			waited = 0;
-		} else {
-			waited += it_sleep_us;
-		}
-
-		usleep(it_sleep_us);
-	}
-
-	return err;
-}
-
-/**
- * @brief Read the contents of the pipe, appending the result to supplied the supplied
- *   'std::string' till 'read()' returns '0' or error.
- *
- * @param pipe_fd The file descriptor from which to read.
- * @param sbuffer An 'std::string' in which to append the data readed from the pipe.
- *
- * @return '0' if all the contents of the pipe were read properly, otherwise '-1' is
- *   returned and 'errno' holds the error code for the failed 'read()'.
- */
-int read_pipe(int pipe_fd, std::string& sbuffer) {
-	char buffer[128];
-	ssize_t count = 0;
-	int res = 1;
-
-	for (;;) {
-		count = read(pipe_fd, (void*)buffer, sizeof(buffer) - 1);
-		if (count > 0) {
-		buffer[count] = 0;
-			sbuffer += buffer;
-		} else if (count == 0){
-			res = 0;
-			break;
-		} else {
-			if (errno != EWOULDBLOCK && errno != EINTR) {
-				res = -1;
-			}
-			break;
-		}
-	}
-
-	return res;
-}
-
-/**
- * @brief Returns the current timestamp in microseconds.
- * @return The current timestamp in microseconds.
- */
-uint64_t get_timestamp_us() {
-	struct timeval start_tv {};
-	gettimeofday(&start_tv, nullptr);
-	uint64_t start_timestamp = (1000000ull * start_tv.tv_sec) + start_tv.tv_usec;
-
-	return start_timestamp;
-}
-
-/**
- * @brief Simple struct that holds the 'timeout options' for 'wexecvp'.
- */
-struct to_opts {
-	/**
-	 * @brief Timeout for the script execution to be completed, in case of being
-	 *   exceeded, the script will be terminated.
-	 */
-	uint timeout_us;
-	/**
-	 * @brief Timeout used for 'select()' non blocking calls.
-	 */
-	uint select_to_us;
-	/**
-	 * @brief The duration of the sleeps between the checks being performed
-	 *   on the child process waiting it to exit after being signaled to terminate,
-	 *   before issuing 'SIGKILL'.
-	 */
-	uint it_delay_us;
-	/**
-	 * @brief The timeout to be waited on the child process after being signaled
-	 *   with 'SIGTERM' before being forcely terminated by 'SIGKILL'.
-	 */
-	uint sigkill_timeout_us;
-};
-
-/**
- * @brief Helper function to launch an executable in a child process through 'fork()' and
- *   'execvp()' and retrieve their 'stderr' and 'stdout' to the caller toguether with
- *   the result of it's execution.
- *
- * @param file The file to be executed.
- * @param argv The arguments to be supplied to the file being executed.
- * @param opts Struct holding timeout options to consider for the launched process.
- * @param s_stdout Output string to hold the output 'stdout' of the child process.
- * @param s_stderr Output string to hold the output 'stderr' of the child process.
- *
- * @return 0 In case of success or one of the following error codes:
- *   - '-1' in case any 'pipe()' creation failed.
- *   - '-2' in case 'fork()' call failed.
- *   - '-3' in case 'fcntl()' call failed .
- *   - '-4' in case of resource exhaustion, file descriptors being used exceeds 'FD_SETSIZE'.
- *   - '-5' in case 'select()' call failed.
- *   - '-6' in case 'read()' from pipes failed with a non-expected error.
- *   - 'ETIME' in case the executable has exceeded the timeout supplied in 'opts.timeout_us'.
- *  In all this cases 'errno' is set to the error reported by the failing 'system call'.
- */
-int wexecvp(
-	const std::string& file,
-	const std::vector<const char*>& argv,
-	const to_opts* opts,
-	std::string& s_stdout,
-	std::string& s_stderr
-) {
-	// Pipes definition
-	constexpr uint8_t NUM_PIPES = 3;
-	constexpr uint8_t PARENT_WRITE_PIPE = 0;
-	constexpr uint8_t PARENT_READ_PIPE  = 1;
-	constexpr uint8_t PARENT_ERR_PIPE   = 2;
-	int pipes[NUM_PIPES][2];
-	// Pipe selection
-	constexpr uint8_t READ_FD  = 0;
-	constexpr uint8_t WRITE_FD = 1;
-	// Parent pipes
-	const auto& PARENT_READ_FD  = pipes[PARENT_READ_PIPE][READ_FD];
-	const auto& PARENT_READ_ERR = pipes[PARENT_ERR_PIPE][READ_FD];
-	const auto& PARENT_WRITE_FD = pipes[PARENT_WRITE_PIPE][WRITE_FD];
-	// Child pipes
-	const auto& CHILD_READ_FD   = pipes[PARENT_WRITE_PIPE][READ_FD];
-	const auto& CHILD_WRITE_FD  = pipes[PARENT_READ_PIPE][WRITE_FD];
-	const auto& CHILD_WRITE_ERR = pipes[PARENT_ERR_PIPE][WRITE_FD];
-
-	int err = 0;
-	to_opts to_opts { 0, 100*1000, 500*1000, 2000*1000 };
-
-	if (opts) {
-		to_opts.timeout_us = opts->timeout_us;
-		to_opts.it_delay_us = opts->it_delay_us;
-		to_opts.select_to_us = opts->select_to_us;
-		to_opts.sigkill_timeout_us = opts->sigkill_timeout_us;
-	}
-
-	// Pipes for parent to write and read
-	int read_p_err = pipe(pipes[PARENT_READ_PIPE]);
-	int write_p_err = pipe(pipes[PARENT_WRITE_PIPE]);
-	int err_p_err = pipe(pipes[PARENT_ERR_PIPE]);
-
-	if (read_p_err || write_p_err || err_p_err) {
-		return -1;
-	}
-
-	pid_t child_pid = fork();
-	if (child_pid == -1) {
-		return -2;
-	}
-
-	if(child_pid == 0) {
-		int child_err = 0;
-		std::vector<const char*> _argv = argv;
-
-		// Append null to end of _argv for extra safety
-		_argv.push_back(nullptr);
-		// Duplicate file argument to avoid manual duplication
-		_argv.insert(_argv.begin(), file.c_str());
-
-		// Copy the pipe descriptors
-		int dup_read_err = dup2(CHILD_READ_FD, STDIN_FILENO);
-		int dup_write_err = dup2(CHILD_WRITE_FD, STDOUT_FILENO);
-		int dup_err_err = dup2(CHILD_WRITE_ERR, STDERR_FILENO);
-
-		if (dup_read_err == -1 || dup_write_err == -1 || dup_err_err == -1) {
-			exit(errno);
-		}
-
-		// Close no longer needed pipes
-		close(CHILD_READ_FD);
-		close(CHILD_WRITE_FD);
-		close(CHILD_WRITE_ERR);
-
-		close(PARENT_READ_FD);
-		close(PARENT_READ_ERR);
-		close(PARENT_WRITE_FD);
-
-		char** args = const_cast<char**>(_argv.data());
-		child_err = execvp(file.c_str(), args);
-
-		if (child_err) {
-			exit(errno);
-		} else {
-			exit(0);
-		}
-	} else {
-		int errno_cpy = 0;
-		int pipe_err = 0;
-
-		std::string stdout_ = "";
-		std::string stderr_ = "";
-
-		// Close no longer needed pipes
-		close(CHILD_READ_FD);
-		close(CHILD_WRITE_FD);
-		close(CHILD_WRITE_ERR);
-
-		// Set the pipes in non-blocking mode
-		int cntl_non_err = fcntl(PARENT_READ_FD, F_SETFL, fcntl(PARENT_READ_FD, F_GETFL) | O_NONBLOCK);
-		int cntl_err_err = fcntl(PARENT_READ_ERR, F_SETFL, fcntl(PARENT_READ_ERR, F_GETFL) | O_NONBLOCK);
-
-		fd_set read_fds;
-		int maxfd = PARENT_READ_FD > PARENT_READ_ERR ? PARENT_READ_FD : PARENT_READ_ERR;
-
-		bool stdout_eof = false;
-		bool stderr_eof = false;
-
-		// Record the start timestamp
-		uint64_t start_timestamp = get_timestamp_us();
-
-		if ((cntl_err_err || cntl_non_err) || maxfd >= FD_SETSIZE) {
-			close(PARENT_READ_FD);
-			close(PARENT_READ_ERR);
-			close(PARENT_WRITE_FD);
-
-			if (cntl_err_err || cntl_non_err) {
-				pipe_err = -3;
-			} else {
-				pipe_err = -4;
-			}
-
-			// Kill child and return error
-			kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
-			// Recover errno before return
-			errno = errno_cpy;
-			// Avoid loop
-			goto loop_exit;
-		}
-
-		while (!stdout_eof || !stderr_eof) {
-			FD_ZERO(&read_fds);
-			FD_SET(PARENT_READ_FD, &read_fds);
-			FD_SET(PARENT_READ_ERR, &read_fds);
-
-			// Wait for the pipes to be ready
-			timeval select_to = { 0, to_opts.select_to_us };
-			int select_err = select(maxfd + 1, &read_fds, NULL, NULL, &select_to);
-
-			// Unexpected error while executing 'select'
-			if (select_err < 0) {
-				pipe_err = -5;
-				// Backup read errno
-				errno_cpy = errno;
-				// Kill child and return error
-				kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
-				// Recover errno before return
-				errno = errno_cpy;
-				// Exit the loop
-				break;
-			}
-
-			if (FD_ISSET(PARENT_READ_FD, &read_fds) && stdout_eof == false) {
-				int read_res = read_pipe(PARENT_READ_FD, stdout_);
-
-				if (read_res == 0) {
-					stdout_eof = true;
-				}
-				// Unexpected error while reading pipe
-				if (read_res < 0) {
-					pipe_err = -6;
-					// Backup read errno
-					errno_cpy = errno;
-					// Kill child and return error
-					kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
-					// Recover errno before return
-					errno = errno_cpy;
-					// Exit the loop
-					break;
-				}
-			}
-
-			if(FD_ISSET(PARENT_READ_ERR, &read_fds) && stderr_eof == false) {
-				int read_res = read_pipe(PARENT_READ_ERR, stderr_);
-
-				if (read_res == 0) {
-					stderr_eof = true;
-				}
-				// Unexpected error while reading pipe
-				if (read_res < 0) {
-					pipe_err = -6;
-					// Backup read errno
-					errno_cpy = errno;
-					// Kill child and return error
-					kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
-					// Recover errno before return
-					errno = errno_cpy;
-					// Exit the loop
-					break;
-				}
-			}
-
-			// Check that the execution hasn't execeed the specified timeout
-			if (to_opts.timeout_us != 0) {
-				uint64_t current_timestamp = get_timestamp_us();
-				if ((start_timestamp + to_opts.timeout_us) < current_timestamp) {
-					// Backup read errno
-					errno_cpy = errno;
-					kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
-					// Recover errno before return
-					errno = errno_cpy;
-					// Set 'pipe_err' to 'ETIME' to reflect timeout
-					pipe_err = ETIME;
-					// Exit the loop
-					break;
-				}
-			}
-		}
-
-loop_exit:
-
-		// Close no longer needed pipes
-		close(PARENT_READ_FD);
-		close(PARENT_READ_ERR);
-		close(PARENT_WRITE_FD);
-
-		if (pipe_err == 0) {
-			waitpid(child_pid, &err, 0);
-		}
-
-		if (pipe_err == 0) {
-			s_stdout = stdout_;
-			s_stderr = stderr_;
-		} else {
-			err = pipe_err;
-		}
-	}
-
-	return err;
-}
-
-/**
- * @brief Helper function to replace all the occurrences in a string of a matching substring in favor
- *   of another string.
- *
- * @param str The string which copy is going to be searched for matches to be replaced.
- * @param match The substring to be matched inside the string.
- * @param repl The string for which matches are going to be replaced.
- *
- * @return A string in which all the matches of 'match' within 'str' has been replaced by 'repl'.
- */
-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;
-}
-
 class sync_resource : public http_resource {
 private:
 	void add_headers(std::shared_ptr<http_response> &response) {
diff --git a/lib/proxysql_utils.cpp b/lib/proxysql_utils.cpp
new file mode 100644
index 0000000000..64da63c2e0
--- /dev/null
+++ b/lib/proxysql_utils.cpp
@@ -0,0 +1,330 @@
+#include "proxysql_utils.h"
+
+#include <functional>
+#include <sstream>
+
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/**
+ * @brief Kills the given process sending an initial 'SIGTERM' to it. If the process doesn't
+ *   respond within 'timeout_us' to the initial signal a 'SIGKILL' is issued to it.
+ *
+ * @param child_pid The process pid to be terminated.
+ * @param timeout_us The timeout to be waited after sending the initial 'SIGTERM' before
+ *   signaling the process with SIGKILL.
+ * @param it_sleep_us The microseconds to sleep while waiting for the process to terminate.
+ *
+ * @return The error returned by 'kill' in case of it failing.
+ */
+int kill_child_proc(pid_t child_pid, const uint timeout_us, const uint it_sleep_us) {
+	uint err = 0;
+	uint waited = 0;
+	int child_status = 0;
+
+	err = kill(child_pid, SIGTERM);
+
+	while (waitpid(child_pid, &child_status, WNOHANG) == 0) {
+		if (waited >= timeout_us) {
+			kill(child_pid, SIGKILL);
+			waited = 0;
+		} else {
+			waited += it_sleep_us;
+		}
+
+		usleep(it_sleep_us);
+	}
+
+	return err;
+}
+
+/**
+ * @brief Read the contents of the pipe, appending the result to supplied the supplied
+ *   'std::string' till 'read()' returns '0' or error.
+ *
+ * @param pipe_fd The file descriptor from which to read.
+ * @param sbuffer An 'std::string' in which to append the data readed from the pipe.
+ *
+ * @return '0' if all the contents of the pipe were read properly, otherwise '-1' is
+ *   returned and 'errno' holds the error code for the failed 'read()'.
+ */
+int read_pipe(int pipe_fd, std::string& sbuffer) {
+	char buffer[128];
+	ssize_t count = 0;
+	int res = 1;
+
+	for (;;) {
+		count = read(pipe_fd, (void*)buffer, sizeof(buffer) - 1);
+		if (count > 0) {
+		buffer[count] = 0;
+			sbuffer += buffer;
+		} else if (count == 0){
+			res = 0;
+			break;
+		} else {
+			if (errno != EWOULDBLOCK && errno != EINTR) {
+				res = -1;
+			}
+			break;
+		}
+	}
+
+	return res;
+}
+
+uint64_t get_timestamp_us() {
+	struct timeval start_tv {};
+	gettimeofday(&start_tv, nullptr);
+	uint64_t start_timestamp = (1000000ull * start_tv.tv_sec) + start_tv.tv_usec;
+
+	return start_timestamp;
+}
+
+int wexecvp(
+	const std::string& file,
+	const std::vector<const char*>& argv,
+	const to_opts* opts,
+	std::string& s_stdout,
+	std::string& s_stderr
+) {
+	// Pipes definition
+	constexpr uint8_t NUM_PIPES = 3;
+	constexpr uint8_t PARENT_WRITE_PIPE = 0;
+	constexpr uint8_t PARENT_READ_PIPE  = 1;
+	constexpr uint8_t PARENT_ERR_PIPE   = 2;
+	int pipes[NUM_PIPES][2];
+	// Pipe selection
+	constexpr uint8_t READ_FD  = 0;
+	constexpr uint8_t WRITE_FD = 1;
+	// Parent pipes
+	const auto& PARENT_READ_FD  = pipes[PARENT_READ_PIPE][READ_FD];
+	const auto& PARENT_READ_ERR = pipes[PARENT_ERR_PIPE][READ_FD];
+	const auto& PARENT_WRITE_FD = pipes[PARENT_WRITE_PIPE][WRITE_FD];
+	// Child pipes
+	const auto& CHILD_READ_FD   = pipes[PARENT_WRITE_PIPE][READ_FD];
+	const auto& CHILD_WRITE_FD  = pipes[PARENT_READ_PIPE][WRITE_FD];
+	const auto& CHILD_WRITE_ERR = pipes[PARENT_ERR_PIPE][WRITE_FD];
+
+	int err = 0;
+	to_opts to_opts { 0, 100*1000, 500*1000, 2000*1000 };
+
+	if (opts) {
+		to_opts.timeout_us = opts->timeout_us;
+		to_opts.it_delay_us = opts->it_delay_us;
+		to_opts.select_to_us = opts->select_to_us;
+		to_opts.sigkill_timeout_us = opts->sigkill_timeout_us;
+	}
+
+	// Pipes for parent to write and read
+	int read_p_err = pipe(pipes[PARENT_READ_PIPE]);
+	int write_p_err = pipe(pipes[PARENT_WRITE_PIPE]);
+	int err_p_err = pipe(pipes[PARENT_ERR_PIPE]);
+
+	if (read_p_err || write_p_err || err_p_err) {
+		return -1;
+	}
+
+	pid_t child_pid = fork();
+	if (child_pid == -1) {
+		return -2;
+	}
+
+	if(child_pid == 0) {
+		int child_err = 0;
+		std::vector<const char*> _argv = argv;
+
+		// Append null to end of _argv for extra safety
+		_argv.push_back(nullptr);
+		// Duplicate file argument to avoid manual duplication
+		_argv.insert(_argv.begin(), file.c_str());
+
+		// Copy the pipe descriptors
+		int dup_read_err = dup2(CHILD_READ_FD, STDIN_FILENO);
+		int dup_write_err = dup2(CHILD_WRITE_FD, STDOUT_FILENO);
+		int dup_err_err = dup2(CHILD_WRITE_ERR, STDERR_FILENO);
+
+		if (dup_read_err == -1 || dup_write_err == -1 || dup_err_err == -1) {
+			exit(errno);
+		}
+
+		// Close no longer needed pipes
+		close(CHILD_READ_FD);
+		close(CHILD_WRITE_FD);
+		close(CHILD_WRITE_ERR);
+
+		close(PARENT_READ_FD);
+		close(PARENT_READ_ERR);
+		close(PARENT_WRITE_FD);
+
+		char** args = const_cast<char**>(_argv.data());
+		child_err = execvp(file.c_str(), args);
+
+		if (child_err) {
+			exit(errno);
+		} else {
+			exit(0);
+		}
+	} else {
+		int errno_cpy = 0;
+		int pipe_err = 0;
+
+		std::string stdout_ = "";
+		std::string stderr_ = "";
+
+		// Close no longer needed pipes
+		close(CHILD_READ_FD);
+		close(CHILD_WRITE_FD);
+		close(CHILD_WRITE_ERR);
+
+		// Set the pipes in non-blocking mode
+		int cntl_non_err = fcntl(PARENT_READ_FD, F_SETFL, fcntl(PARENT_READ_FD, F_GETFL) | O_NONBLOCK);
+		int cntl_err_err = fcntl(PARENT_READ_ERR, F_SETFL, fcntl(PARENT_READ_ERR, F_GETFL) | O_NONBLOCK);
+
+		fd_set read_fds;
+		int maxfd = PARENT_READ_FD > PARENT_READ_ERR ? PARENT_READ_FD : PARENT_READ_ERR;
+
+		bool stdout_eof = false;
+		bool stderr_eof = false;
+
+		// Record the start timestamp
+		uint64_t start_timestamp = get_timestamp_us();
+
+		if ((cntl_err_err || cntl_non_err) || maxfd >= FD_SETSIZE) {
+			close(PARENT_READ_FD);
+			close(PARENT_READ_ERR);
+			close(PARENT_WRITE_FD);
+
+			if (cntl_err_err || cntl_non_err) {
+				pipe_err = -3;
+			} else {
+				pipe_err = -4;
+			}
+
+			// Kill child and return error
+			kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
+			// Recover errno before return
+			errno = errno_cpy;
+			// Avoid loop
+			goto loop_exit;
+		}
+
+		while (!stdout_eof || !stderr_eof) {
+			FD_ZERO(&read_fds);
+			FD_SET(PARENT_READ_FD, &read_fds);
+			FD_SET(PARENT_READ_ERR, &read_fds);
+
+			// Wait for the pipes to be ready
+			timeval select_to = { 0, to_opts.select_to_us };
+			int select_err = select(maxfd + 1, &read_fds, NULL, NULL, &select_to);
+
+			// Unexpected error while executing 'select'
+			if (select_err < 0) {
+				pipe_err = -5;
+				// Backup read errno
+				errno_cpy = errno;
+				// Kill child and return error
+				kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
+				// Recover errno before return
+				errno = errno_cpy;
+				// Exit the loop
+				break;
+			}
+
+			if (FD_ISSET(PARENT_READ_FD, &read_fds) && stdout_eof == false) {
+				int read_res = read_pipe(PARENT_READ_FD, stdout_);
+
+				if (read_res == 0) {
+					stdout_eof = true;
+				}
+				// Unexpected error while reading pipe
+				if (read_res < 0) {
+					pipe_err = -6;
+					// Backup read errno
+					errno_cpy = errno;
+					// Kill child and return error
+					kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
+					// Recover errno before return
+					errno = errno_cpy;
+					// Exit the loop
+					break;
+				}
+			}
+
+			if(FD_ISSET(PARENT_READ_ERR, &read_fds) && stderr_eof == false) {
+				int read_res = read_pipe(PARENT_READ_ERR, stderr_);
+
+				if (read_res == 0) {
+					stderr_eof = true;
+				}
+				// Unexpected error while reading pipe
+				if (read_res < 0) {
+					pipe_err = -6;
+					// Backup read errno
+					errno_cpy = errno;
+					// Kill child and return error
+					kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
+					// Recover errno before return
+					errno = errno_cpy;
+					// Exit the loop
+					break;
+				}
+			}
+
+			// Check that the execution hasn't execeed the specified timeout
+			if (to_opts.timeout_us != 0) {
+				uint64_t current_timestamp = get_timestamp_us();
+				if ((start_timestamp + to_opts.timeout_us) < current_timestamp) {
+					// Backup read errno
+					errno_cpy = errno;
+					kill_child_proc(child_pid, to_opts.sigkill_timeout_us, to_opts.it_delay_us);
+					// Recover errno before return
+					errno = errno_cpy;
+					// Set 'pipe_err' to 'ETIME' to reflect timeout
+					pipe_err = ETIME;
+					// Exit the loop
+					break;
+				}
+			}
+		}
+
+loop_exit:
+
+		// Close no longer needed pipes
+		close(PARENT_READ_FD);
+		close(PARENT_READ_ERR);
+		close(PARENT_WRITE_FD);
+
+		if (pipe_err == 0) {
+			waitpid(child_pid, &err, 0);
+		} else {
+			err = pipe_err;
+		}
+
+		if (pipe_err == 0 || pipe_err == ETIME) {
+			s_stdout = stdout_;
+			s_stderr = stderr_;
+		}
+	}
+
+	return err;
+}
+
+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;
+}


Test patch:
diff --git a/test/tap/tap/utils.cpp b/test/tap/tap/utils.cpp
index b465c11e0a..b367322266 100644
--- a/test/tap/tap/utils.cpp
+++ b/test/tap/tap/utils.cpp
@@ -122,51 +122,6 @@ int get_server_version(MYSQL *mysql, std::string& version) {
 	return 0;
 }
 
-int kill_child_proc(pid_t child_pid, const uint timeout_us, const uint it_sleep_us) {
-	uint err = 0;
-	uint waited = 0;
-	int child_status = 0;
-
-	err = kill(child_pid, SIGTERM);
-
-	while (waitpid(child_pid, &child_status, WNOHANG) == 0) {
-		if (waited >= timeout_us) {
-			kill(child_pid, SIGKILL);
-			waited = 0;
-		} else {
-			waited += it_sleep_us;
-		}
-
-		usleep(it_sleep_us);
-	}
-
-	return err;
-}
-
-int read_pipe(int pipe_fd, std::string& sbuffer) {
-	char buffer[128];
-	ssize_t count = 0;
-	int res = 1;
-
-	for (;;) {
-		count = read(pipe_fd, (void*)buffer, sizeof(buffer) - 1);
-		if (count > 0) {
-			buffer[count] = 0;
-			sbuffer += buffer;
-		} else if (count == 0){
-			res = 0;
-			break;
-		} else {
-			if (errno != EWOULDBLOCK && errno != EINTR) {
-				res = -1;
-			}
-			break;
-		}
-	}
-
-	return res;
-}
-
 int add_more_rows_test_sbtest1(int num_rows, MYSQL *mysql) {
 	std::random_device rd;
 	std::mt19937 mt(rd());
@@ -218,156 +173,6 @@ int create_table_test_sbtest1(int num_rows, MYSQL *mysql) {
 	return add_more_rows_test_sbtest1(num_rows, mysql);
 }
 
-int wexecvp(const std::string& file, const std::vector<const char*>& argv, const to_opts* opts, std::string& s_stdout, std::string& s_stderr) {
-	// Pipes definition
-	constexpr uint8_t NUM_PIPES = 3;
-	constexpr uint8_t PARENT_WRITE_PIPE = 0;
-	constexpr uint8_t PARENT_READ_PIPE  = 1;
-	constexpr uint8_t PARENT_ERR_PIPE   = 2;
-	int pipes[NUM_PIPES][2];
-	// Pipe selection
-	constexpr uint8_t READ_FD  = 0;
-	constexpr uint8_t WRITE_FD = 1;
-	// Parent pipes
-	const auto& PARENT_READ_FD  = pipes[PARENT_READ_PIPE][READ_FD];
-	const auto& PARENT_READ_ERR = pipes[PARENT_ERR_PIPE][READ_FD];
-	const auto& PARENT_WRITE_FD = pipes[PARENT_WRITE_PIPE][WRITE_FD];
-	// Child pipes
-	const auto& CHILD_READ_FD   = pipes[PARENT_WRITE_PIPE][READ_FD];
-	const auto& CHILD_WRITE_FD  = pipes[PARENT_READ_PIPE][WRITE_FD];
-	const auto& CHILD_WRITE_ERR = pipes[PARENT_ERR_PIPE][WRITE_FD];
-
-	int err = 0;
-	to_opts to_opts { 1000*1000, 100*1000, 500*1000 };
-
-	// Pipes for parent to write and read
-	pipe(pipes[PARENT_READ_PIPE]);
-	pipe(pipes[PARENT_WRITE_PIPE]);
-	pipe(pipes[PARENT_ERR_PIPE]);
-
-	pid_t child_pid = fork();
-	if(child_pid == 0) {
-		std::vector<const char*> _argv = argv;
-
-		// Append null to end of _argv for extra safety
-		_argv.push_back(nullptr);
-		// Duplicate file argument to avoid manual duplication
-		_argv.insert(_argv.begin(), file.c_str());
-
-		if (opts) {
-			to_opts.timeout_us = opts->timeout_us;
-			to_opts.it_delay_us = opts->it_delay_us;
-		}
-
-		// Copy the pipe descriptors
-		dup2(CHILD_READ_FD, STDIN_FILENO);
-		dup2(CHILD_WRITE_FD, STDOUT_FILENO);
-		dup2(CHILD_WRITE_ERR, STDERR_FILENO);
-
-		// Close no longer needed pipes
-		close(CHILD_READ_FD);
-		close(CHILD_WRITE_FD);
-		close(CHILD_WRITE_ERR);
-
-		close(PARENT_READ_FD);
-		close(PARENT_READ_ERR);
-		close(PARENT_WRITE_FD);
-
-		char** args = const_cast<char**>(_argv.data());
-		err = execvp(file.c_str(), args);
-
-		if (err) {
-			exit(errno);
-		} else {
-			exit(0);
-		}
-	} else {
-		int errno_cpy = 0;
-		int pipe_err = 0;
-
-		std::string stdout_ = "";
-		std::string stderr_ = "";
-
-		// Close no longer needed pipes
-		close(CHILD_READ_FD);
-		close(CHILD_WRITE_FD);
-		close(CHILD_WRITE_ERR);
-
-		// Set the pipes in non-blocking mode
-		fcntl(PARENT_READ_FD, F_SETFL, fcntl(PARENT_READ_FD, F_GETFL) | O_NONBLOCK);
-		fcntl(PARENT_READ_ERR, F_SETFL, fcntl(PARENT_READ_ERR, F_GETFL) | O_NONBLOCK);
-
-		fd_set read_fds;
-		int maxfd = PARENT_READ_FD > PARENT_READ_ERR ? PARENT_READ_FD : PARENT_READ_ERR;
-
-		bool stdout_eof = false;
-		bool stderr_eof = false;
-
-		while (!stdout_eof || !stderr_eof) {
-			FD_ZERO(&read_fds);
-			FD_SET(PARENT_READ_FD, &read_fds);
-			FD_SET(PARENT_READ_ERR, &read_fds);
-
-			// Wait for the pipes to be ready
-			timeval select_to = { 0, to_opts.select_to_us };
-			select(maxfd + 1, &read_fds, NULL, NULL, &select_to);
-
-			if (FD_ISSET(PARENT_READ_FD, &read_fds) && stdout_eof == false) {
-				int read_res = read_pipe(PARENT_READ_FD, stdout_);
-
-				if (read_res == 0) {
-					stdout_eof = true;
-				}
-				// Unexpected error while reading pipe
-				if (read_res < 0) {
-					pipe_err = -1;
-					// Backup read errno
-					errno_cpy = errno;
-					// Kill child and return error
-					kill_child_proc(child_pid, to_opts.timeout_us, to_opts.it_delay_us);
-					// Recover errno before return
-					errno = errno_cpy;
-					// Exit the loop
-					break;
-				}
-			}
-
-			if(FD_ISSET(PARENT_READ_ERR, &read_fds) && stderr_eof == false) {
-				int read_res = read_pipe(PARENT_READ_ERR, stderr_);
-
-				if (read_res == 0) {
-					stderr_eof = true;
-				}
-				// Unexpected error while reading pipe
-				if (read_res < 0) {
-					pipe_err = -1;
-					// Backup read errno
-					errno_cpy = errno;
-					// Kill child and return error
-					kill_child_proc(child_pid, to_opts.timeout_us, to_opts.it_delay_us);
-					// Recover errno before return
-					errno = errno_cpy;
-					// Exit the loop
-					break;
-				}
-			}
-		}
-
-		if (pipe_err == 0) {
-			waitpid(child_pid, &err, 0);
-		}
-
-		if (pipe_err == 0) {
-			s_stdout = stdout_;
-			s_stderr = stderr_;
-		} else {
-			err = pipe_err;
-		}
-	}
-
-	return err;
-}
-
 int execvp(const std::string& cmd, const std::vector<const char*>& argv, std::string& result) {
 	// Pipes definition
 	constexpr uint8_t NUM_PIPES = 3;
@@ -836,3 +641,27 @@ int get_proxysql_cpu_usage(const CommandLine& cl, uint32_t intv, uint32_t& cpu_u
 
 	return 0;
 }
+
+MYSQL* wait_for_proxysql(const conn_opts_t& opts, int timeout) {
+	uint con_waited = 0;
+	MYSQL* admin = mysql_init(NULL);
+
+	const char* user = opts.user.c_str();
+	const char* pass = opts.pass.c_str();
+	const char* host = opts.host.c_str();
+	const int port = opts.port;
+
+	while (!mysql_real_connect(admin, host, user, pass, NULL, port, NULL, 0) && con_waited < timeout) {
+		mysql_close(admin);
+		admin = mysql_init(NULL);
+
+		con_waited += 1;
+		sleep(1);
+	}
+
+	if (con_waited >= timeout) {
+		return nullptr;
+	} else {
+		return admin;
+	}
+}
diff --git a/test/tap/tap/utils.h b/test/tap/tap/utils.h
index f83df178bc..cb5c2243aa 100644
--- a/test/tap/tap/utils.h
+++ b/test/tap/tap/utils.h
@@ -43,30 +43,6 @@ int select_config_file(MYSQL* mysql, std::string& resultset);
 }
 #endif
 
-/**
- * @brief Simple struct that holds the 'timeout options' for 'wexecvp'.
- */
-struct to_opts {
-	uint timeout_us;
-	uint it_delay_us;
-	uint select_to_us;
-};
-
-/**
- * @brief Execute the given comand, and stores it's output.
- *
- * @param file File to be executed.
- * @param argv Arguments to be given to the executable.
- * @param result The output of the file execution. If the execution succeed it contains `stdout` output,
- *  in case of failure `stderr` contents are returned.
- * @param opts In case of pipe readin error, this timeout options are used for trying to terminate
- *  the child process nicely, before seding a SIGKILL to it:
- *    - timeout_us: Member specifies the total timeout to wait for the child to exit.
- *    - it_delay_us: Member specifies the waiting delay between checks.
- * @return int Zero in case of success, or the errno returned by `execvp` in case of failure.
- */
-int wexecvp(const std::string& file, const std::vector<const char*>& argv, const to_opts* opts, std::string& s_stdout, std::string& s_stderr);
-
 /*
  * @return int Zero in case of success, or the errno returned by `execvp` in case of failure.
  */
@@ -240,4 +216,26 @@ std::string tap_curtime();
  */
 int get_proxysql_cpu_usage(const CommandLine& cl, uint32_t intv, uint32_t& cpu_usage);
 
+/**
+ * @brief Helper struct holding connection options for helper functions creating MySQL connections.
+ */
+struct conn_opts_t {
+	std::string host;
+	std::string user;
+	std::string pass;
+	int port;
+	uint64_t client_flags;
+};
+
+/**
+ * @brief Helper function for waiting until ProxySQL is replying to client connections.
+ *
+ * @param opts The options to be used for performing the MySQL connection.
+ * @param timeout Timeout for the wait.
+ *
+ * @return An opened MySQL* connection in case a connection could be created before timeout expired,
+ *  'nullptr' otherwise.
+ */
+MYSQL* wait_for_proxysql(const conn_opts_t& opts, int timeout);
+
 #endif // #define 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 ed409b742f..539fd4ea82 100644
--- a/test/tap/tests/reg_test_3765_ssl_pollout-t.cpp
+++ b/test/tap/tests/reg_test_3765_ssl_pollout-t.cpp
@@ -26,14 +26,6 @@
 using std::string;
 using std::vector;
 
-struct conn_opts_t {
-	string host;
-	string user;
-	string pass;
-	int port;
-	uint64_t client_flags;
-};
-
 /**
  * @brief TODO: Refactor this into utils, also used in another PR.
  */
diff --git a/test/tap/tests/reg_test_3847_admin_lock-t.cpp b/test/tap/tests/reg_test_3847_admin_lock-t.cpp
new file mode 100644
index 0000000000..de92226bb1
--- /dev/null
+++ b/test/tap/tests/reg_test_3847_admin_lock-t.cpp
@@ -0,0 +1,225 @@
+/**
+ * @file reg_test_3847_admin_lock-t.cpp
+ * @brief This is a regression test for the deadlock described in issue #3847.
+ */
+
+#include <cstring>
+#include <vector>
+#include <string>
+#include <thread>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <mysql.h>
+#include <mysql/mysqld_error.h>
+
+#include "proxysql_utils.h"
+#include "tap.h"
+#include "command_line.h"
+#include "utils.h"
+
+using std::string;
+using std::vector;
+
+int main(int argc, char** argv) {
+	CommandLine cl;
+
+	const char* WORKSPACE = getenv("WORKSPACE");
+
+	if (cl.getEnv() || WORKSPACE == nullptr) {
+		diag("Failed to get the required environmental variables.");
+		return EXIT_FAILURE;
+	}
+
+	bool stop = false;
+	int q_load_res = -1;
+	int q_globals_res = -1;
+
+	MYSQL* p_proxy_admin = mysql_init(NULL);
+
+	if (!mysql_real_connect(p_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(p_proxy_admin));
+		return EXIT_FAILURE;
+	}
+
+	const string sec_cfg_file = string { cl.workdir } + "reg_test_3847_node_datadir/proxysql_sec.cfg";
+	const string sec_log_file = string { cl.workdir } + "reg_test_3847_node_datadir/proxysql_sec.log";
+
+	const string sec_proxy_cmd {
+		string { "ASAN_OPTIONS=abort_on_error=0:halt_on_error=0:fast_unwind_on_fatal=1:detect_leaks=0 " } +
+			string { WORKSPACE } + "/src/proxysql -M -c \"" + sec_cfg_file + "\" > " + sec_log_file + " 2>&1"
+	};
+
+	int launch_res = -1;
+
+	std::thread launch_sec_proxy = std::thread([&WORKSPACE,&cl] (int& err_code) -> void {
+		to_opts wexecvp_opts {};
+		wexecvp_opts.select_to_us = 100*1000;
+		wexecvp_opts.it_delay_us = 500*1000;
+		// Stop launched process after 20s
+		wexecvp_opts.timeout_us = 20000 * 1000;
+		// Send sigkill 3s after timeout
+		wexecvp_opts.sigkill_timeout_us = 3000 * 1000;
+
+		const string sec_cfg_file = string { cl.workdir } + "reg_test_3847_node_datadir/proxysql_sec.cfg";
+		const string sec_log_file = string { cl.workdir } + "reg_test_3847_node_datadir/proxysql_sec.log";
+		const string proxysql_path { string { WORKSPACE } + "/src/proxysql" };
+
+		const vector<const char*> proxy_args { "-f", "-M", "-c", sec_cfg_file.c_str() };
+
+		string s_stdout {};
+		string s_stderr {};
+
+		int w_res = wexecvp(proxysql_path, proxy_args, &wexecvp_opts, s_stdout, s_stderr);
+		if (w_res != EXIT_SUCCESS) {
+			diag("'wexecvp' failed with error: %d", w_res);
+		}
+
+		err_code = w_res;
+
+		// Write process output to log file
+		try {
+			std::ofstream os_logfile { sec_log_file, std::ios::out };
+			os_logfile << s_stderr;
+		} catch (const std::exception& ex) {
+			fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, ex.what());
+		}
+	}, std::ref(launch_res));
+
+	// Check that the second ProxySQL is up and responsive
+	conn_opts_t conn_opts { "127.0.0.1", "radmin", "radmin", 26081 };
+	MYSQL* s_proxy_admin = wait_for_proxysql(conn_opts, 5);
+
+	if (s_proxy_admin == nullptr) {
+		fprintf(stderr, "Error: %s\n", "Waiting for ProxySQL replica timedout");
+		return EXIT_FAILURE;
+	}
+
+	// Configure Cluster access for primary ProxySQL
+	MYSQL_QUERY(p_proxy_admin, "SET admin-admin_credentials='admin:admin;radmin:radmin;cluster1:secret1pass'");
+	MYSQL_QUERY(p_proxy_admin, "SET admin-cluster_username='cluster1'");
+	MYSQL_QUERY(p_proxy_admin, "SET admin-cluster_password='secret1pass'");
+	MYSQL_QUERY(p_proxy_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
+
+	// Configure secondary node
+	MYSQL_QUERY(s_proxy_admin, "SET admin-cluster_check_interval_ms=10");
+	MYSQL_QUERY(s_proxy_admin, "SET admin-cluster_mysql_variables_diffs_before_sync=1");
+	MYSQL_QUERY(s_proxy_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
+
+	std::thread th_load_mysql_vars([&cl] (bool& stop, int& load_res) -> void {
+		MYSQL* admin = mysql_init(NULL);
+
+		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));
+			load_res = mysql_errno(admin);
+		}
+
+		int default_query_timeout = 36000000;
+
+		while (stop == false) {
+			string set_query { "SET mysql-default_query_timeout=" + std::to_string(default_query_timeout) };
+			mysql_query(admin, set_query.c_str());
+			int my_res = mysql_query(admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+
+			if (my_res) {
+				load_res = mysql_errno(admin);
+				break;
+			} else {
+				usleep(1000 * 10 * 2);
+			}
+
+			if (default_query_timeout > 36000000) {
+				default_query_timeout = 36000000;
+			} else {
+				default_query_timeout += 1;
+			}
+		}
+
+		load_res = 0;
+
+		mysql_close(admin);
+	}, std::ref(stop), std::ref(q_load_res));
+
+	std::thread th_query_globals([&cl] (bool& stop, int& save_res) -> void {
+		MYSQL* admin = mysql_init(NULL);
+
+		if (!mysql_real_connect(admin, cl.host, cl.admin_username, cl.admin_password, NULL, 26081, NULL, 0)) {
+			fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(admin));
+			save_res = mysql_errno(admin);
+		}
+
+		while (stop == false) {
+			int my_res = mysql_query(admin, "SELECT COUNT(*) FROM runtime_global_variables");
+			if (my_res) {
+				save_res = mysql_errno(admin);
+				break;
+			} else {
+				mysql_free_result(mysql_store_result(admin));
+			}
+		}
+
+		save_res = 0;
+
+		mysql_close(admin);
+	}, std::ref(stop), std::ref(q_globals_res));
+
+	uint32_t timeout = 10;
+	uint32_t wait = 0;
+
+	while (wait < timeout) {
+		if (q_globals_res != -1) {
+			fprintf(stderr, "'th_admin_save' failed with error: %d\n", q_globals_res);
+			break;
+		}
+		if (q_load_res != -1) {
+			fprintf(stderr, "'th_admin_load' failed with error: %d\n", q_load_res);
+			break;
+		}
+
+		sleep(1);
+		wait += 1;
+	}
+
+	{
+		diag("Shutting down worker threads");
+		int timeout = 2;
+		int waited = 0;
+
+		stop = true;
+
+		if ((q_globals_res == -1 || q_load_res == -1) && waited < timeout) {
+			sleep(1);
+			waited += 1;
+		}
+	}
+
+	ok(q_load_res == 0, "'th_load_mysql_vars' thread didn't deadlock: %d", q_load_res);
+	ok(q_globals_res == 0, "'th_query_globals' thread didn't deadlock: %d", q_globals_res);
+
+	th_load_mysql_vars.detach();
+	th_query_globals.detach();
+
+	{
+		// NOTE: Can lock for a max of 20s (child process timeout)
+		diag("Shutting down ProxySQL replica");
+		mysql_query(s_proxy_admin, "PROXYSQL SHUTDOWN SLOW");
+
+		int timeout = 3;
+		int waited = 0;
+
+		// Wait for shutdown
+		if (launch_res == -1 && waited < timeout) {
+			sleep(1);
+			waited += 1;
+		}
+
+		ok(launch_res == 0, "Replica was properly shutdown and no deadlock took place");
+	}
+
+	launch_sec_proxy.join();
+
+	mysql_close(p_proxy_admin);
+	mysql_close(s_proxy_admin);
+
+	return exit_status();
+}
diff --git a/test/tap/tests/reg_test_3847_node_datadir/proxysql_sec.cfg b/test/tap/tests/reg_test_3847_node_datadir/proxysql_sec.cfg
new file mode 100644
index 0000000000..45f866d043
--- /dev/null
+++ b/test/tap/tests/reg_test_3847_node_datadir/proxysql_sec.cfg
@@ -0,0 +1,31 @@
+cluster_sync_interfaces=false
+restart_on_missing_heartbeats=3
+
+admin_variables=
+{
+	admin_credentials="admin:admin;cluster1:secret1pass"
+	mysql_ifaces="0.0.0.0:26081"
+	cluster_username="cluster1"
+	cluster_password="secret1pass"
+}
+
+mysql_variables=
+{
+	interfaces="0.0.0.0:36081"
+}
+
+proxysql_servers =
+(
+	{
+		hostname="127.0.0.1"
+		port=26081
+		weight=0
+		comment="replica"
+	},
+	{
+		hostname="127.0.0.1"
+		port=6032
+		weight=0
+		comment="primary"
+	}
+)
diff --git a/test/tap/tests/test_cluster_sync-t.cpp b/test/tap/tests/test_cluster_sync-t.cpp
index e55e953332..385f7e85f2 100644
--- a/test/tap/tests/test_cluster_sync-t.cpp
+++ b/test/tap/tests/test_cluster_sync-t.cpp
@@ -1,3 +1,31 @@
+/**
+ * @file test_cluster_sync-t.cpp
+ * @brief Checks that ProxySQL is properly syncing multiple elements from another Cluster instance.
+ * @details Checks the sync of the following tables:
+ *   - 'mysql_galera_hostgroups' with and without NULL comments.
+ *   - 'mysql_group_replication_hostgroups' with and without NULL comments.
+ *   - 'proxysql_servers' with new values and empty (exercising bug from '#3847').
+ *   - 'mysql_aws_aurora_hostgroups' with and without NULL comments.
+ *   - 'mysql_variables'.
+ *   - 'admin_variables'.
+ *
+ *  Test Cluster Isolation:
+ *  ----------------------
+ *  For guaranteeing that this test doesn't invalidate the configuration of a running ProxySQL cluster and
+ *  that after the test, the previous valid configuration is restored, the following actions are performed:
+ *
+ *  1. The Core nodes from the current cluster configuration are backup.
+ *  2. Primary (currently tested instance) is removed from the Core nodes.
+ *  3. A sync wait until all core nodes have performed the removal of primary is executed.
+ *  4. Now Primary is isolated from the previous cluster, tests can proceed. Primary is setup to hold itself
+ *     in its 'proxysql_servers' as well as the target spawned replica.
+ *  5. After the tests recover the primary configuration and add it back to the Core nodes from Cluster:
+ *      - Recover the previous 'mysql_servers' from disk, and load them to runtime, discarding any previous
+ *        config performed during the test.
+ *      - Insert the primary back into a Core node from cluster and wait for all nodes to sync including it.
+ *      - Insert into the primary the previous backup Core nodes from Cluster and load to runtime.
+ */
+
 #include <unistd.h>
 #include <signal.h>
 #include <pthread.h>
@@ -30,8 +58,65 @@
 		} \
 	} while(0)
 
+using std::string;
+using std::vector;
+
+/**
+ * @brief Helper function to verify that the sync of a table (or variable) have been performed.
+ *
+ * @param r_proxy_admin An already opened connection to ProxySQL.
+ * @param queries Queries to be executed that should return a **non-zero** value after the sync has taken place.
+ * @param sync_timeout Timeout for the sync to happen.
+ *
+ * @return EXIT_SUCCESS in case of success, otherwise:
+ *  - '-1' if a query against Admin fails to be performed (failure is logged).
+ *  - '-2' if timeout expired without sync being completed.
+ */
+int sync_checker(MYSQL* r_proxy_admin, const vector<string>& queries, uint32_t sync_timeout) {
+	bool not_synced_query = false;
+	uint waited = 0;
+
+	while (waited < sync_timeout) {
+		not_synced_query = false;
+
+		// Check that all the entries have been synced
+		for (const auto& query : queries) {
+			int q_res = mysql_query(r_proxy_admin, query.c_str());
+			if (q_res != EXIT_SUCCESS) {
+				fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(r_proxy_admin));
+				return -1;
+			}
+
+			MYSQL_RES* proxysql_servers_res = mysql_store_result(r_proxy_admin);
+			MYSQL_ROW row = mysql_fetch_row(proxysql_servers_res);
+			int row_value = atoi(row[0]);
+			mysql_free_result(proxysql_servers_res);
+
+			if (row_value == 0) {
+				not_synced_query = true;
+				break;
+			}
+		}
+
+		if (not_synced_query) {
+			waited += 1;
+			sleep(1);
+		} else {
+			break;
+		}
+	}
+
+	if (not_synced_query) {
+		return -2;
+	} else {
+		return EXIT_SUCCESS;
+	}
+}
+
+// GLOBAL TEST PARAMETERS
 const uint32_t SYNC_TIMEOUT = 10;
-const uint32_t CONNECT_TIMEOUT = 60;
+const uint32_t CONNECT_TIMEOUT = 10;
+const uint32_t R_PORT = 96062;
 
 int setup_config_file(const CommandLine& cl) {
 	const std::string t_fmt_config_file = std::string(cl.workdir) + "test_cluster_sync_config/test_cluster_sync-t.cnf";
@@ -49,50 +134,62 @@ int setup_config_file(const CommandLine& cl) {
 		config_destroy(&cfg);
 		return -1;
 	}
+
+	config_setting_t* r_datadir = config_lookup(&cfg, "datadir");
+	if (r_datadir == nullptr) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'datadir' setting not found.");
+		return -1;
+	}
+
+	config_setting_t* r_admin_vars = config_lookup(&cfg, "admin_variables");
+	if (r_admin_vars == nullptr) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'admin_variables' setting not found.");
+		return -1;
+	}
+
+	config_setting_t* r_mysql_ifaces = config_setting_get_member(r_admin_vars, "mysql_ifaces");
+	if (r_mysql_ifaces == nullptr) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'mysql_ifaces' setting not found.");
+		return -1;
+	}
+
+	int r_ifaces_res = config_setting_set_string(r_mysql_ifaces, string { "0.0.0.0:"  + std::to_string(R_PORT) }.c_str());
+	if (r_ifaces_res == CONFIG_FALSE) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - Error while trying to set the values for 'mysql_ifaces'.");
+		return -1;
+	}
+
 	config_setting_t* p_servers = config_lookup(&cfg, "proxysql_servers");
 	if (p_servers == nullptr) {
 		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'proxysql_servers' setting not found.");
 		return -1;
 	}
 
-	// Get first group settings
-	config_setting_t* f_pserver_group = config_setting_get_elem(p_servers, 0);
-	if (f_pserver_group == nullptr) {
-		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'proxysql_servers' doesn't contains first group.");
+	int r_datadir_res = config_setting_set_string(r_datadir, string { string { cl.workdir } + "test_cluster_sync_config" }.c_str());
+	if (r_datadir_res == CONFIG_FALSE) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - Error while trying to set the 'datadir' value.");
 		return -1;
 	}
-	config_setting_t* f_pserver_hostname = config_setting_get_member(f_pserver_group, "hostname");
-	config_setting_t* f_pserver_port = config_setting_get_member(f_pserver_group, "port");
 
-	// Get second group settings
-	config_setting_t* s_pserver_group = config_setting_get_elem(p_servers, 1);
-	if (f_pserver_group == nullptr) {
-		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'proxysql_servers' doesn't contains second group.");
+	// Get first group settings
+	config_setting_t* r_pserver_group = config_setting_get_elem(p_servers, 0);
+	if (r_pserver_group == nullptr) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'proxysql_servers' doesn't contains first group.");
 		return -1;
 	}
-	config_setting_t* s_pserver_hostname = config_setting_get_member(s_pserver_group, "hostname");
-	config_setting_t* s_pserver_port = config_setting_get_member(s_pserver_group, "port");
+	config_setting_t* r_pserver_hostname = config_setting_get_member(r_pserver_group, "hostname");
+	config_setting_t* r_pserver_port = config_setting_get_member(r_pserver_group, "port");
 
 	// Check the group members
-	if (f_pserver_hostname == nullptr || f_pserver_port == nullptr || s_pserver_hostname == nullptr || s_pserver_port == nullptr) {
+	if (r_pserver_hostname == nullptr || r_pserver_port == nullptr) {
 		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - 'proxysql_servers' doesn't contains the necessary group members.");
 		return -1;
 	}
 
-	// Get the current docker bridge ip address
-	std::string bridge_addr = "";
-	int bridge_res = exec("ip -4 addr show docker0 | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'", bridge_addr);
-	if (bridge_res != 0) {
-		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Failed to get docker bridge ip - Not able to write the config file.");
-		return -1;
-	}
-
-	int fhost_res = config_setting_set_string(f_pserver_hostname, bridge_addr.substr(0, bridge_addr.size() - 1).c_str());
-	int fport_res = config_setting_set_int(f_pserver_port, cl.admin_port);
-	int shost_res = config_setting_set_string(s_pserver_hostname, cl.host);
-	int sport_res = config_setting_set_int(s_pserver_port, 6032);
+	int fhost_res = config_setting_set_string(r_pserver_hostname, cl.host);
+	int fport_res = config_setting_set_int(r_pserver_port, cl.admin_port);
 
-	if (fhost_res == CONFIG_FALSE || fport_res == CONFIG_FALSE || shost_res == CONFIG_FALSE || sport_res == CONFIG_FALSE) {
+	if (fhost_res == CONFIG_FALSE || fport_res == CONFIG_FALSE) {
 		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, "Invalid config file - Error while trying to set the values from env variables.");
 		return -1;
 	}
@@ -108,6 +205,30 @@ int setup_config_file(const CommandLine& cl) {
 	return 0;
 }
 
+int check_nodes_sync(
+	const CommandLine& cl, const vector<mysql_res_row>& core_nodes, const string& check_query, uint32_t sync_timeout
+) {
+	for (const auto& node : core_nodes) {
+		const string host { node[0] };
+		const int port = std::stol(node[1]);
+
+		MYSQL* c_node_admin = mysql_init(NULL);
+		if (!mysql_real_connect(c_node_admin, host.c_str(), cl.admin_username, cl.admin_password, NULL, port, NULL, 0)) {
+			fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(c_node_admin));
+			return EXIT_FAILURE;
+		}
+
+		int not_synced = sync_checker(c_node_admin, { check_query }, sync_timeout);
+		if (not_synced != EXIT_SUCCESS) {
+			const string err_msg { "Node '"  + host + ":" + std::to_string(port) + "' sync timed out" };
+			fprintf(stderr, "File %s, line %d, Error: `%s`\n", __FILE__, __LINE__, err_msg.c_str());
+			return EXIT_FAILURE;
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+
 int main(int, char**) {
 	int res = 0;
 	CommandLine cl;
@@ -115,87 +236,118 @@ int main(int, char**) {
 
 	if (cl.getEnv()) {
 		diag("Failed to get the required environmental variables.");
-		return -1;
+		return EXIT_FAILURE;
 	}
 
+	plan(12);
+
 	const std::string fmt_config_file = std::string(cl.workdir) + "test_cluster_sync_config/test_cluster_sync.cnf";
 	const std::string t_debug_query = "mysql -u%s -p%s -h %s -P%d -C -e \"%s\"";
 
-	MYSQL* proxysql_admin = mysql_init(NULL);
-	MYSQL* proxysql_replica = mysql_init(NULL);
+	MYSQL* proxy_admin = mysql_init(NULL);
 
 	// Initialize connections
-	if (!proxysql_admin) {
-		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_admin));
-		return -1;
-	}
-	if (!proxysql_replica) {
-		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_replica));
-		return -1;
+	if (!proxy_admin) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxy_admin));
+		return EXIT_FAILURE;
 	}
 
 	// Connnect to local proxysql
-	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;
+	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));
+		return EXIT_FAILURE;
 	}
 
-	const std::string t_update_proxysql_servers =
-		"INSERT INTO proxysql_servers (hostname, port, weight, comment) "
-		"VALUES ('%s', %d, 0, 'proxysql130'), "
-		"('%s', %d, 0, 'proxysql131')";
-
-	std::string bridge_addr = "";
-	int bridge_res = exec("ip -4 addr show docker0 | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'", bridge_addr);
+	const std::string t_update_proxysql_servers {
+		"INSERT INTO proxysql_servers (hostname, port, weight, comment) VALUES ('%s', %d, 0, 'proxysql')"
+	};
 
 	std::string update_proxysql_servers = "";
-	string_format(t_update_proxysql_servers, update_proxysql_servers, bridge_addr.substr(0, bridge_addr.size() - 1).c_str(), cl.admin_port, cl.host, 6032);
+	string_format(t_update_proxysql_servers, update_proxysql_servers, cl.host, cl.admin_port);
 
 	// Setup the config file using the env variables in 'CommandLine'
 	if (setup_config_file(cl)) {
-		return -1;
+		return EXIT_FAILURE;
 	}
 
-	// Configure local proxysql instance
-	MYSQL_QUERY(proxysql_admin, "DELETE FROM proxysql_servers");
-	MYSQL_QUERY(proxysql_admin, update_proxysql_servers.c_str());
-	MYSQL_QUERY(proxysql_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+	// 1. Backup the Core nodes from current cluster configuration
+	MYSQL_QUERY(proxy_admin, "DROP TABLE IF EXISTS proxysql_servers_sync_test_backup_2687");
+	MYSQL_QUERY(proxy_admin, "CREATE TABLE proxysql_servers_sync_test_backup_2687 AS SELECT * FROM proxysql_servers");
+
+	// 2. Remove primary from Core nodes
+	MYSQL_QUERY(proxy_admin, "DELETE FROM proxysql_servers WHERE hostname=='127.0.0.1' AND PORT==6032");
+	MYSQL_QUERY(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+	MYSQL_QUERY(proxy_admin, "SELECT hostname,port FROM proxysql_servers");
+	MYSQL_RES* my_res = mysql_store_result(proxy_admin);
+	vector<mysql_res_row> core_nodes { extract_mysql_rows(my_res) };
+	mysql_free_result(my_res);
+
+	// 3. Wait for all Core nodes to sync (confirm primary out of core nodes)
+	string check_no_primary_query {};
+	string_format(
+		"SELECT CASE COUNT(*) WHEN 0 THEN 1 ELSE 0 END FROM proxysql_servers WHERE hostname=='%s' AND port==%d",
+		check_no_primary_query, cl.host, cl.admin_port
+	);
+
+	int check_res = check_nodes_sync(cl, core_nodes, check_no_primary_query, SYNC_TIMEOUT);
+	if (check_res != EXIT_SUCCESS) { return EXIT_FAILURE; }
+
+	// 4. Remove all current servers from primary instance (only secondary sync matters)
+	MYSQL_QUERY(proxy_admin, "DELETE FROM proxysql_servers");
+	MYSQL_QUERY(proxy_admin, update_proxysql_servers.c_str());
+	MYSQL_QUERY(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
 
 	// Launch proxysql with cluster config
 	std::thread proxy_replica_th([&save_proxy_stderr, &cl] () {
-		const std::string cluster_sync_node_stderr = std::string(cl.workdir) + "test_cluster_sync_config/cluster_sync_node_stderr.txt";
+		const std::string cluster_sync_node_stderr {
+			std::string(cl.workdir) + "test_cluster_sync_config/cluster_sync_node_stderr.txt"
+		};
 		const std::string proxysql_db = std::string(cl.workdir) + "test_cluster_sync_config/proxysql.db";
 		const std::string stats_db = std::string(cl.workdir) + "test_cluster_sync_config/proxysql_stats.db";
+		const std::string fmt_config_file = std::string(cl.workdir) + "test_cluster_sync_config/test_cluster_sync.cnf";
 
-		const std::string docker_command =
-			std::string("docker run -p 16032:6032 -e ASAN_OPTIONS=abort_on_error=0:halt_on_error=0:fast_unwind_on_fatal=1:detect_leaks=0 ") +
-			"-v " + std::string(cl.workdir) + "../../../:/tmp/proxysql ubuntu:20.04 sh -c" +
-			" \"./tmp/proxysql/src/proxysql -f -M -c /tmp/proxysql/test/tap/tests/test_cluster_sync_config/test_cluster_sync.cnf\" " +
-			std::string("> ") + cluster_sync_node_stderr + " 2>&1";
+		std::string proxy_stdout {};
+		std::string proxy_stderr {};
+		int exec_res = wexecvp(
+			std::string(cl.workdir) + "../../../src/proxysql", { "-f", "-M", "-c", fmt_config_file.c_str() }, NULL,
+			proxy_stdout, proxy_stderr
+		);
 
-		int exec_res = system(docker_command.c_str());
 		ok(exec_res == 0, "proxysql cluster node should execute and shutdown nicely. 'wexecvp' result was: %d", exec_res);
 
+		// In case of error place in log the reason
+		if (exec_res || save_proxy_stderr.load()) {
+			if (exec_res) {
+				diag("LOG: Proxysql cluster node execution failed, logging stderr into 'test_cluster_sync_node_stderr.txt'");
+			} else {
+				diag("LOG: One of the tests failed to pass, logging stderr 'test_cluster_sync_node_stderr.txt'");
+			}
+		}
+
+		// Always log child process output to file
+		std::ofstream error_log_file {};
+		error_log_file.open(cluster_sync_node_stderr);
+		error_log_file << proxy_stderr;
+		error_log_file.close();
+
 		remove(proxysql_db.c_str());
 		remove(stats_db.c_str());
 	});
 
 	// Waiting for proxysql to be ready
-	uint con_waited = 0;
-	while (!mysql_real_connect(proxysql_replica, cl.host, "radmin", "radmin", NULL, 16032, NULL, 0) && con_waited < CONNECT_TIMEOUT) {
-		mysql_close(proxysql_replica);
-		proxysql_replica = mysql_init(NULL);
+	conn_opts_t conn_opts {};
+	conn_opts.host = cl.host;
+	conn_opts.user = "radmin";
+	conn_opts.pass = "radmin";
+	conn_opts.port = 96062;
 
-		con_waited += 1;
-		sleep(1);
-	}
+	MYSQL* r_proxy_admin = wait_for_proxysql(conn_opts, CONNECT_TIMEOUT);
 
 	// Once the thread is spanwed we should always go to cleanup to wait
-	bool con_err = mysql_errno(proxysql_replica) != 0;
-	ok(con_err == 0, "New instance of proxysql with cluster config should be properly spawned.");
+	ok(r_proxy_admin != nullptr, "New instance of proxysql with cluster config should be properly spawned.");
 
-	if (con_err) {
-		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(proxysql_replica));
+	if (r_proxy_admin == nullptr) {
+		fprintf(stderr, "File %s, line %d, Error: %s\n", __FILE__, __LINE__, mysql_error(r_proxy_admin));
 		res = -1;
 		goto cleanup;
 	}
@@ -206,7 +358,7 @@ int main(int, char**) {
 		std::string print_master_galera_hostgroups = "";
 		string_format(t_debug_query, print_master_galera_hostgroups, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_mysql_galera_hostgroups");
 		std::string print_replica_galera_hostgroups = "";
-		string_format(t_debug_query, print_replica_galera_hostgroups, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_mysql_galera_hostgroups");
+		string_format(t_debug_query, print_replica_galera_hostgroups, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_mysql_galera_hostgroups");
 
 		// Configure 'mysql_galera_hostgroups' and check sync with NULL comments
 		const char* t_insert_mysql_galera_hostgroups =
@@ -266,14 +418,14 @@ int main(int, char**) {
 		// SETUP CONFIG
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE mysql_galera_hostgroups_sync_test_2687 AS SELECT * FROM mysql_galera_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_galera_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE mysql_galera_hostgroups_sync_test_2687 AS SELECT * FROM mysql_galera_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_galera_hostgroups");
 
 		// Insert the new galera hostgroups values
 		for (const auto& query : insert_mysql_galera_hostgroup_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_galera_hostgroups.c_str());
@@ -287,8 +439,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_galera_hostgroup_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* galera_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* galera_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(galera_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(galera_res);
@@ -312,10 +464,10 @@ int main(int, char**) {
 		ok(not_synced_query == false, "'mysql_galera_hostgroups' with NULL comments should be synced.");
 
 		// TEARDOWN CONFIG
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_galera_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_galera_hostgroups SELECT * FROM mysql_galera_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE mysql_galera_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_galera_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO mysql_galera_hostgroups SELECT * FROM mysql_galera_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE mysql_galera_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 	}
 
 	sleep(2);
@@ -324,7 +476,7 @@ int main(int, char**) {
 		std::string print_master_galera_hostgroups = "";
 		string_format(t_debug_query, print_master_galera_hostgroups, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_mysql_galera_hostgroups");
 		std::string print_replica_galera_hostgroups = "";
-		string_format(t_debug_query, print_replica_galera_hostgroups, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_mysql_galera_hostgroups");
+		string_format(t_debug_query, print_replica_galera_hostgroups, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_mysql_galera_hostgroups");
 
 		// Configure 'mysql_galera_hostgroups' and check sync
 		const char* t_insert_mysql_galera_hostgroups =
@@ -384,14 +536,14 @@ int main(int, char**) {
 		}
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE mysql_galera_hostgroups_sync_test_2687 AS SELECT * FROM mysql_galera_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_galera_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE mysql_galera_hostgroups_sync_test_2687 AS SELECT * FROM mysql_galera_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_galera_hostgroups");
 
 		// Insert the new galera hostgroups values
 		for (const auto& query : insert_mysql_galera_hostgroup_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_galera_hostgroups.c_str());
 
@@ -402,8 +554,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_galera_hostgroup_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* galera_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* galera_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(galera_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(galera_res);
@@ -427,10 +579,10 @@ int main(int, char**) {
 		ok(not_synced_query == false, "'mysql_galera_hostgroups' should be synced.");
 
 		// TEARDOWN CONFIG
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_galera_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_galera_hostgroups SELECT * FROM mysql_galera_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE mysql_galera_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_galera_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO mysql_galera_hostgroups SELECT * FROM mysql_galera_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE mysql_galera_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 	}
 
 	sleep(2);
@@ -440,7 +592,7 @@ int main(int, char**) {
 		std::string print_master_group_replication_hostgroups = "";
 		string_format(t_debug_query, print_master_group_replication_hostgroups, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_mysql_group_replication_hostgroups");
 		std::string print_replica_group_replication_hostgroups = "";
-		string_format(t_debug_query, print_replica_group_replication_hostgroups, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_mysql_group_replication_hostgroups");
+		string_format(t_debug_query, print_replica_group_replication_hostgroups, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_mysql_group_replication_hostgroups");
 
 		// Configure 'mysql_group_replication_hostgroups' and check sync
 		const char* t_insert_mysql_group_replication_hostgroups =
@@ -500,14 +652,14 @@ int main(int, char**) {
 		// SETUP CONFIG
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE mysql_group_replication_hostgroups_sync_test_2687 AS SELECT * FROM mysql_group_replication_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_group_replication_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE mysql_group_replication_hostgroups_sync_test_2687 AS SELECT * FROM mysql_group_replication_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_group_replication_hostgroups");
 
 		// Insert the new group_replication hostgroups values
 		for (const auto& query : insert_mysql_group_replication_hostgroup_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_group_replication_hostgroups.c_str());
 
@@ -519,8 +671,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_group_replication_hostgroup_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* group_replication_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* group_replication_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(group_replication_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(group_replication_res);
@@ -544,10 +696,10 @@ int main(int, char**) {
 		ok(not_synced_query == false, "'mysql_group_replication_hostgroups' with NULL comments should be synced.");
 
 		// TEARDOWN CONFIG
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_group_replication_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_group_replication_hostgroups SELECT * FROM mysql_group_replication_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE mysql_group_replication_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_group_replication_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO mysql_group_replication_hostgroups SELECT * FROM mysql_group_replication_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE mysql_group_replication_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 	}
 
 	sleep(2);
@@ -557,7 +709,7 @@ int main(int, char**) {
 		std::string print_master_group_replication_hostgroups = "";
 		string_format(t_debug_query, print_master_group_replication_hostgroups, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_mysql_group_replication_hostgroups");
 		std::string print_replica_group_replication_hostgroups = "";
-		string_format(t_debug_query, print_replica_group_replication_hostgroups, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_mysql_group_replication_hostgroups");
+		string_format(t_debug_query, print_replica_group_replication_hostgroups, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_mysql_group_replication_hostgroups");
 
 		// Configure 'mysql_group_replication_hostgroups' and check sync
 		const char* t_insert_mysql_group_replication_hostgroups =
@@ -619,14 +771,14 @@ int main(int, char**) {
 		// SETUP CONFIG
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE mysql_group_replication_hostgroups_sync_test_2687 AS SELECT * FROM mysql_group_replication_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_group_replication_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE mysql_group_replication_hostgroups_sync_test_2687 AS SELECT * FROM mysql_group_replication_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_group_replication_hostgroups");
 
 		// Insert the new group_replication hostgroups values
 		for (const auto& query : insert_mysql_group_replication_hostgroup_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_group_replication_hostgroups.c_str());
 
@@ -636,8 +788,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_group_replication_hostgroup_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* group_replication_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* group_replication_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(group_replication_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(group_replication_res);
@@ -661,10 +813,159 @@ int main(int, char**) {
 		ok(not_synced_query == false, "'mysql_group_replication_hostgroups' should be synced.");
 
 		// TEARDOWN CONFIG
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_group_replication_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_group_replication_hostgroups SELECT * FROM mysql_group_replication_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE mysql_group_replication_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_group_replication_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO mysql_group_replication_hostgroups SELECT * FROM mysql_group_replication_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE mysql_group_replication_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+	}
+
+	sleep(2);
+
+	// Check 'proxysql_servers' synchronization with random values
+	{
+		std::string print_master_proxysql_servers = "";
+		string_format(
+			t_debug_query, print_master_proxysql_servers, cl.admin_username, cl.admin_password, cl.host,
+			cl.admin_port, "SELECT * FROM runtime_proxysql_servers"
+		);
+		std::string print_replica_proxysql_servers = "";
+		string_format(
+			t_debug_query, print_replica_proxysql_servers, "radmin", "radmin", cl.host, R_PORT,
+			"SELECT * FROM runtime_proxysql_servers"
+		);
+
+		// Configure 'proxysql_servers' and check sync
+		const char* t_insert_proxysql_servers =
+			"INSERT INTO proxysql_servers (hostname, port, weight, comment) VALUES ('%s', %d, %d, '%s')";
+
+		std::string invalid_server_01_comment {};
+		std::string invalid_server_02_comment {};
+		std::string invalid_server_03_comment {};
+		std::string invalid_server_04_comment {};
+
+		std::vector<std::tuple<const char*,int,int,std::string>> insert_proxysql_servers_values {};
+
+		std::string s_host = "127.0.0.1";
+		uint32_t s_port = 26091;
+		std::string s_base_comment { "invalid_server_" };
+
+		for (uint32_t i = 0; i < 5; i++) {
+			uint32_t weight = rand() % 10;
+			std::string s_comment { "invalid_server_" + std::to_string(i) + "_" + std::to_string(rand() % 40) };
+
+			insert_proxysql_servers_values.push_back(std::make_tuple(s_host.c_str(), s_port + i, 1, s_comment.c_str()));
+		}
+
+		std::vector<std::string> insert_proxysql_servers_queries {};
+
+		for (auto const& values : insert_proxysql_servers_values) {
+			std::string insert_proxysql_servers_query {};
+			string_format(
+				t_insert_proxysql_servers,
+				insert_proxysql_servers_query,
+				std::get<0>(values),
+				std::get<1>(values),
+				std::get<2>(values),
+				std::get<3>(values).c_str()
+			);
+			insert_proxysql_servers_queries.push_back(insert_proxysql_servers_query);
+		}
+
+		const char* t_select_proxysql_servers_inserted_entries =
+			"SELECT COUNT(*) FROM proxysql_servers WHERE hostname='%s' AND port=%d AND weight=%d AND comment='%s'";
+		std::vector<std::string> select_proxysql_servers_queries {};
+
+		for (auto const& values : insert_proxysql_servers_values) {
+			std::string select_proxysql_servers_query = "";
+			string_format(
+				t_select_proxysql_servers_inserted_entries,
+				select_proxysql_servers_query,
+				std::get<0>(values),
+				std::get<1>(values),
+				std::get<2>(values),
+				std::get<3>(values).c_str()
+			);
+			select_proxysql_servers_queries.push_back(select_proxysql_servers_query);
+		}
+
+		// SETUP CONFIG
+
+		// Backup current table
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE IF EXISTS proxysql_servers_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE proxysql_servers_sync_test_2687 AS SELECT * FROM proxysql_servers");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM proxysql_servers WHERE comment LIKE '%invalid_server_%'");
+
+		// Insert the new proxysql_servers values
+		for (const auto& query : insert_proxysql_servers_queries) {
+			MYSQL_QUERY__(proxy_admin, query.c_str());
+		}
+		MYSQL_QUERY__(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+
+		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
+		system(print_master_proxysql_servers.c_str());
+
+		int check_res = sync_checker(r_proxy_admin, select_proxysql_servers_queries, SYNC_TIMEOUT);
+
+		std::cout << "REPLICA TABLE AFTER SYNC:" << std::endl;
+		system(print_replica_proxysql_servers.c_str());
+
+		ok(check_res == EXIT_SUCCESS, "'proxysql_servers' with should be synced: '%d'", check_res);
+
+		// TEARDOWN CONFIG
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM proxysql_servers");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO proxysql_servers SELECT * FROM proxysql_servers_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE proxysql_servers_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+	}
+
+	// Check 'proxysql_servers' synchronization with no servers (forcing '0x00' checksum)
+	{
+		std::string print_master_proxysql_servers = "";
+		string_format(
+			t_debug_query, print_master_proxysql_servers, cl.admin_username, cl.admin_password, cl.host,
+			cl.admin_port, "SELECT * FROM runtime_proxysql_servers"
+		);
+		std::string print_replica_proxysql_servers = "";
+		string_format(
+			t_debug_query, print_replica_proxysql_servers, "radmin", "radmin", cl.host, R_PORT,
+			"SELECT * FROM runtime_proxysql_servers"
+		);
+
+		// 1. Backup ProxySQL servers config in replica
+		MYSQL_QUERY__(r_proxy_admin, "SAVE PROXYSQL SERVERS TO DISK");
+		MYSQL_QUERY__(r_proxy_admin, "DROP TABLE IF EXISTS proxysql_servers_backup");
+		MYSQL_QUERY__(r_proxy_admin, "CREATE TABLE proxysql_servers_backup AS SELECT * FROM proxysql_servers");
+
+		// 2. Backup and delete ProxySQL servers from main ProxySQL
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE IF EXISTS proxysql_servers_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE proxysql_servers_sync_test_2687 AS SELECT * FROM proxysql_servers");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM proxysql_servers");
+		MYSQL_QUERY__(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+
+		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
+		system(print_master_proxysql_servers.c_str());
+
+		// 3. Check that the servers have been synced in the replica
+		int check_res =
+			sync_checker(
+				r_proxy_admin, { "SELECT CASE count(*) WHEN 0 THEN 1 ELSE 0 END from proxysql_servers" }, SYNC_TIMEOUT
+			);
+
+		std::cout << "REPLICA TABLE AFTER SYNC:" << std::endl;
+		system(print_replica_proxysql_servers.c_str());
+
+		// 3. Recover ProxySQL servers in the primary
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM proxysql_servers");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO proxysql_servers SELECT * FROM proxysql_servers_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE proxysql_servers_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+
+		// 4. Recover ProxySQL servers in the replica
+		MYSQL_QUERY__(r_proxy_admin, "INSERT INTO proxysql_servers SELECT * FROM proxysql_servers_backup");
+		MYSQL_QUERY__(r_proxy_admin, "DROP TABLE IF EXISTS proxysql_servers_backup");
+		MYSQL_QUERY__(r_proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+
+		ok(check_res == EXIT_SUCCESS, "Empty 'proxysql_servers' table ('0x00' checksum) should be synced: '%d'", check_res);
 	}
 
 	sleep(2);
@@ -674,7 +975,7 @@ int main(int, char**) {
 		std::string print_master_aws_aurora_hostgroups = "";
 		string_format(t_debug_query, print_master_aws_aurora_hostgroups, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_mysql_aws_aurora_hostgroups");
 		std::string print_replica_aws_aurora_hostgroups = "";
-		string_format(t_debug_query, print_replica_aws_aurora_hostgroups, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_mysql_aws_aurora_hostgroups");
+		string_format(t_debug_query, print_replica_aws_aurora_hostgroups, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_mysql_aws_aurora_hostgroups");
 
 		// Configure 'mysql_aws_aurora_hostgroups' and check sync
 		const char* t_insert_mysql_aws_aurora_hostgroups =
@@ -744,14 +1045,14 @@ int main(int, char**) {
 		// SETUP CONFIG
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE mysql_aws_aurora_hostgroups_sync_test_2687 AS SELECT * FROM mysql_aws_aurora_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE mysql_aws_aurora_hostgroups_sync_test_2687 AS SELECT * FROM mysql_aws_aurora_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
 
 		// Insert the new aws_aurora hostgroups values
 		for (const auto& query : insert_mysql_aws_aurora_hostgroup_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_aws_aurora_hostgroups.c_str());
 
@@ -763,8 +1064,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_aws_aurora_hostgroup_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* aws_aurora_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* aws_aurora_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(aws_aurora_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(aws_aurora_res);
@@ -788,10 +1089,10 @@ int main(int, char**) {
 		ok(not_synced_query == false, "'mysql_aws_aurora_hostgroups' with NULL comments should be synced.");
 
 		// TEARDOWN CONFIG
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_aws_aurora_hostgroups SELECT * FROM mysql_aws_aurora_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE mysql_aws_aurora_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO mysql_aws_aurora_hostgroups SELECT * FROM mysql_aws_aurora_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE mysql_aws_aurora_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 	}
 
 	sleep(2);
@@ -801,7 +1102,7 @@ int main(int, char**) {
 		std::string print_master_aws_aurora_hostgroups = "";
 		string_format(t_debug_query, print_master_aws_aurora_hostgroups, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_mysql_aws_aurora_hostgroups");
 		std::string print_replica_aws_aurora_hostgroups = "";
-		string_format(t_debug_query, print_replica_aws_aurora_hostgroups, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_mysql_aws_aurora_hostgroups");
+		string_format(t_debug_query, print_replica_aws_aurora_hostgroups, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_mysql_aws_aurora_hostgroups");
 
 		// Configure 'mysql_aws_aurora_hostgroups' and check sync
 		const char* t_insert_mysql_aws_aurora_hostgroups =
@@ -871,14 +1172,14 @@ int main(int, char**) {
 		}
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE mysql_aws_aurora_hostgroups_sync_test_2687 AS SELECT * FROM mysql_aws_aurora_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE mysql_aws_aurora_hostgroups_sync_test_2687 AS SELECT * FROM mysql_aws_aurora_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
 
 		// Insert the new aws_aurora hostgroups values
 		for (const auto& query : insert_mysql_aws_aurora_hostgroup_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_aws_aurora_hostgroups.c_str());
 
@@ -888,8 +1189,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_aws_aurora_hostgroup_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* aws_aurora_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* aws_aurora_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(aws_aurora_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(aws_aurora_res);
@@ -913,10 +1214,10 @@ int main(int, char**) {
 		ok(not_synced_query == false, "'mysql_aws_aurora_hostgroups' should be synced.");
 
 		// TEARDOWN CONFIG
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
-		MYSQL_QUERY__(proxysql_admin, "INSERT INTO mysql_aws_aurora_hostgroups SELECT * FROM mysql_aws_aurora_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE mysql_aws_aurora_hostgroups_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM mysql_aws_aurora_hostgroups");
+		MYSQL_QUERY__(proxy_admin, "INSERT INTO mysql_aws_aurora_hostgroups SELECT * FROM mysql_aws_aurora_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE mysql_aws_aurora_hostgroups_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
 	}
 
 	sleep(2);
@@ -926,7 +1227,7 @@ int main(int, char**) {
 		std::string print_master_mysql_variables = "";
 		string_format(t_debug_query, print_master_mysql_variables, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_global_variables WHERE variable_name LIKE 'mysql-%'");
 		std::string print_replica_mysql_variables = "";
-		string_format(t_debug_query, print_replica_mysql_variables, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_global_variables WHERE variable_name LIKE 'mysql-%'");
+		string_format(t_debug_query, print_replica_mysql_variables, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_global_variables WHERE variable_name LIKE 'mysql-%'");
 
 		// Configure 'mysql_mysql_variables_hostgroups' and check sync
 		const char* t_update_mysql_variables =
@@ -1072,13 +1373,13 @@ int main(int, char**) {
 		}
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE global_variables_sync_test_2687 AS SELECT * FROM global_variables WHERE variable_name LIKE 'mysql-%'");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM global_variables WHERE variable_name LIKE 'mysql-%'");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE global_variables_sync_test_2687 AS SELECT * FROM global_variables WHERE variable_name LIKE 'mysql-%'");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM global_variables WHERE variable_name LIKE 'mysql-%'");
 
 		for (const auto& query : update_mysql_variables_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL VARIABLES TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_mysql_variables.c_str());
 
@@ -1089,8 +1390,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_mysql_variables_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* mysql_vars_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* mysql_vars_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(mysql_vars_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(mysql_vars_res);
@@ -1118,9 +1419,9 @@ int main(int, char**) {
 		system(print_replica_mysql_variables.c_str());
 		ok(not_synced_query == false, "'mysql_variables' from global_variables should be synced.");
 
-		MYSQL_QUERY__(proxysql_admin, "INSERT OR REPLACE INTO global_variables SELECT * FROM global_variables_sync_test_2687 WHERE variable_name LIKE 'mysql-%'");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE global_variables_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD MYSQL VARIABLES TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "INSERT OR REPLACE INTO global_variables SELECT * FROM global_variables_sync_test_2687 WHERE variable_name LIKE 'mysql-%'");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE global_variables_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD MYSQL VARIABLES TO RUNTIME");
 	}
 
 	sleep(2);
@@ -1130,7 +1431,7 @@ int main(int, char**) {
 		std::string print_master_admin_variables = "";
 		string_format(t_debug_query, print_master_admin_variables, cl.admin_username, cl.admin_password, cl.host, cl.admin_port, "SELECT * FROM runtime_global_variables WHERE variable_name LIKE 'admin-%'");
 		std::string print_replica_admin_variables = "";
-		string_format(t_debug_query, print_replica_admin_variables, "radmin", "radmin", cl.host, 16032, "SELECT * FROM runtime_global_variables WHERE variable_name LIKE 'admin-%'");
+		string_format(t_debug_query, print_replica_admin_variables, "radmin", "radmin", cl.host, R_PORT, "SELECT * FROM runtime_global_variables WHERE variable_name LIKE 'admin-%'");
 
 		// Configure 'mysql_admin_variables_hostgroups' and check sync
 		const char* t_update_admin_variables =
@@ -1202,13 +1503,13 @@ int main(int, char**) {
 		}
 
 		// Backup current table
-		MYSQL_QUERY__(proxysql_admin, "CREATE TABLE global_variables_sync_test_2687 AS SELECT * FROM global_variables WHERE variable_name LIKE 'admin-%'");
-		MYSQL_QUERY__(proxysql_admin, "DELETE FROM global_variables WHERE variable_name LIKE 'admin-%'");
+		MYSQL_QUERY__(proxy_admin, "CREATE TABLE global_variables_sync_test_2687 AS SELECT * FROM global_variables WHERE variable_name LIKE 'admin-%'");
+		MYSQL_QUERY__(proxy_admin, "DELETE FROM global_variables WHERE variable_name LIKE 'admin-%'");
 
 		for (const auto& query : update_admin_variables_queries) {
-			MYSQL_QUERY__(proxysql_admin, query.c_str());
+			MYSQL_QUERY__(proxy_admin, query.c_str());
 		}
-		MYSQL_QUERY__(proxysql_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
 		std::cout << "MASTER TABLE BEFORE SYNC:" << std::endl;
 		system(print_master_admin_variables.c_str());
 
@@ -1219,8 +1520,8 @@ int main(int, char**) {
 			not_synced_query = false;
 			// Check that all the entries have been synced
 			for (const auto& query : select_admin_variables_queries) {
-				MYSQL_QUERY__(proxysql_replica, query.c_str());
-				MYSQL_RES* admin_vars_res = mysql_store_result(proxysql_replica);
+				MYSQL_QUERY__(r_proxy_admin, query.c_str());
+				MYSQL_RES* admin_vars_res = mysql_store_result(r_proxy_admin);
 				MYSQL_ROW row = mysql_fetch_row(admin_vars_res);
 				int row_value = atoi(row[0]);
 				mysql_free_result(admin_vars_res);
@@ -1248,9 +1549,9 @@ int main(int, char**) {
 		system(print_replica_admin_variables.c_str());
 		ok(not_synced_query == false, "'admin_variables' from global_variables should be synced.");
 
-		MYSQL_QUERY__(proxysql_admin, "INSERT OR REPLACE INTO global_variables SELECT * FROM global_variables_sync_test_2687 WHERE variable_name LIKE 'mysql-%'");
-		MYSQL_QUERY__(proxysql_admin, "DROP TABLE global_variables_sync_test_2687");
-		MYSQL_QUERY__(proxysql_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
+		MYSQL_QUERY__(proxy_admin, "INSERT OR REPLACE INTO global_variables SELECT * FROM global_variables_sync_test_2687 WHERE variable_name LIKE 'mysql-%'");
+		MYSQL_QUERY__(proxy_admin, "DROP TABLE global_variables_sync_test_2687");
+		MYSQL_QUERY__(proxy_admin, "LOAD ADMIN VARIABLES TO RUNTIME");
 	}
 
 cleanup:
@@ -1260,19 +1561,84 @@ int main(int, char**) {
 	if (tests_failed() != 0) {
 		save_proxy_stderr.store(true);
 	}
-	int mysql_timeout = 2;
-	mysql_options(proxysql_replica, MYSQL_OPT_CONNECT_TIMEOUT, &mysql_timeout);
-	mysql_options(proxysql_replica, MYSQL_OPT_READ_TIMEOUT, &mysql_timeout);
-	mysql_options(proxysql_replica, MYSQL_OPT_WRITE_TIMEOUT, &mysql_timeout);
-	mysql_query(proxysql_replica, "PROXYSQL SHUTDOWN");
+
+	if (r_proxy_admin) {
+		int mysql_timeout = 2;
+
+		mysql_options(r_proxy_admin, MYSQL_OPT_CONNECT_TIMEOUT, &mysql_timeout);
+		mysql_options(r_proxy_admin, MYSQL_OPT_READ_TIMEOUT, &mysql_timeout);
+		mysql_options(r_proxy_admin, MYSQL_OPT_WRITE_TIMEOUT, &mysql_timeout);
+		mysql_query(r_proxy_admin, "PROXYSQL SHUTDOWN");
+		mysql_close(r_proxy_admin);
+	}
+
 	proxy_replica_th.join();
-	mysql_close(proxysql_replica);
 
-	remove(fmt_config_file.c_str());
+	// Recover primary ProxySQL MySQL and ProxySQL servers
+	diag("RESTORING: Recovering primary configuration...");
+
+	{
+		// Recover previous MySQL servers and generate a newer checksum for primary
+		MYSQL_QUERY(proxy_admin, "LOAD MYSQL SERVERS FROM DISK");
+		MYSQL_QUERY(proxy_admin, "LOAD MYSQL SERVERS TO RUNTIME");
+
+		// Insert primary into another Core node config and wait for replication
+		diag("RESTORING: Inserting primary back into Core nodes");
+		bool recovered_servers_st = false;
+
+		string insert_query {};
+		string_format(
+			"INSERT INTO proxysql_servers (hostname,port,weight,comment) VALUES ('%s',%d,0,'proxysql')",
+			insert_query, cl.host, cl.admin_port
+		);
+
+		for (const auto& row : core_nodes) {
+			const string host { row[0] };
+			const int port = std::stol(row[1]);
+			MYSQL* c_node_admin = mysql_init(NULL);
+
+			diag("RESTORING: Inserting into node '%s:%d'", host.c_str(), port);
+
+			if (!mysql_real_connect(c_node_admin, host.c_str(), cl.admin_username, cl.admin_password, NULL, port, NULL, 0)) {
+				const string err_msg {
+					"Connection to core node failed with '" + string { mysql_error(c_node_admin) } + "'. Retrying..."
+				};
+				fprintf(stderr, "File %s, line %d, Error: `%s`\n", __FILE__, __LINE__, err_msg.c_str());
+				mysql_close(c_node_admin);
+				continue;
+			}
+
+			int my_rc = mysql_query(c_node_admin, insert_query.c_str());
+			if (my_rc == EXIT_SUCCESS) {
+				mysql_query(c_node_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+				break;
+			} else {
+				const string err_msg {
+					"Insert primary into node failed with: '" + string { mysql_error(c_node_admin) } + "'"
+				};
+				fprintf(stderr, "File %s, line %d, Error: `%s`\n", __FILE__, __LINE__, err_msg.c_str());
+			}
+		}
+
+		// Wait for sync after primary insertion into Core node
+		string check_for_primary {};
+		string_format(
+			"SELECT COUNT(*) FROM proxysql_servers WHERE hostname=='%s' AND port==%d", check_no_primary_query,
+			cl.host, cl.admin_port
+		);
+
+		// Wait for the other nodes to sync ProxySQL servers to include Primary
+		int check_res = check_nodes_sync(cl, core_nodes, check_no_primary_query, SYNC_TIMEOUT);
+		if (check_res != EXIT_SUCCESS) { return EXIT_FAILURE; }
+
+		// Recover the old ProxySQL servers from backup in primary
+		MYSQL_QUERY(proxy_admin, "DELETE FROM proxysql_servers");
+		MYSQL_QUERY(proxy_admin, "INSERT INTO proxysql_servers SELECT * FROM proxysql_servers_sync_test_backup_2687");
+		MYSQL_QUERY(proxy_admin, "DROP TABLE proxysql_servers_sync_test_backup_2687");
+		MYSQL_QUERY(proxy_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
+	}
 
-	MYSQL_QUERY(proxysql_admin, "DELETE FROM proxysql_servers");
-	MYSQL_QUERY(proxysql_admin, "LOAD PROXYSQL SERVERS TO RUNTIME");
-	mysql_close(proxysql_admin);
+	mysql_close(proxy_admin);
 
 	return exit_status();
 }
diff --git a/test/tap/tests/test_cluster_sync_config/test_cluster_sync-t.cnf b/test/tap/tests/test_cluster_sync_config/test_cluster_sync-t.cnf
index 00a3227477..8a51f47dc9 100644
--- a/test/tap/tests/test_cluster_sync_config/test_cluster_sync-t.cnf
+++ b/test/tap/tests/test_cluster_sync_config/test_cluster_sync-t.cnf
@@ -1,9 +1,9 @@
-datadir="/tmp/proxysql/test/tap/tests/test_cluster_sync_config"
+datadir=""
 
 admin_variables=
 {
     admin_credentials="admin:admin;radmin:radmin"
-    mysql_ifaces="0.0.0.0:6032"
+    mysql_ifaces=""
     cluster_username="radmin"
     cluster_password="radmin"
     cluster_check_interval_ms=200
@@ -57,11 +57,5 @@ proxysql_servers =
 		port=0
 		weight=0
 		comment="proxysql130"
-	},
-	{
-		hostname=""
-		port=0
-		weight=0
-		comment="proxysql131"
 	}
 )