From 5bea1f08fe0189dfbcbae4338f24bbf428353488 Mon Sep 17 00:00:00 2001 From: John James Jacoby Date: Mon, 26 Oct 2020 06:55:46 -0500 Subject: [PATCH 1/4] Return false on failure in tcp_cache_set(). This change ensures that anyone calling tcp_cache_set() directly in their own custom code has a more reliable idea of whether this method call has failed. --- ludicrousdb/includes/class-ludicrousdb.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ludicrousdb/includes/class-ludicrousdb.php b/ludicrousdb/includes/class-ludicrousdb.php index 8f7bea9..70f8ec4 100644 --- a/ludicrousdb/includes/class-ludicrousdb.php +++ b/ludicrousdb/includes/class-ludicrousdb.php @@ -2183,7 +2183,7 @@ protected function tcp_cache_set( $key = '', $value = '' ) { // Bail if invalid values were passed if ( empty( $key ) || empty( $value ) ) { - return true; + return false; } // Add value to cache From 5e8cbf9ae9f792b24290da7855f30c963f2e3b54 Mon Sep 17 00:00:00 2001 From: John James Jacoby Date: Mon, 26 Oct 2020 08:44:40 -0500 Subject: [PATCH 2/4] Use local or external TCP cache, but not both. This commit strictly divides the TCP cache methods to stop storing a local copy of the remote cache when a persistent cache exists elsewhere. It also adds expirations to the local cache, in the event that the PHP request is allowed to process and hang for longer than the servers have collectively to be checked. --- ludicrousdb/includes/class-ludicrousdb.php | 102 +++++++++++++++++---- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/ludicrousdb/includes/class-ludicrousdb.php b/ludicrousdb/includes/class-ludicrousdb.php index 70f8ec4..1c23b2f 100644 --- a/ludicrousdb/includes/class-ludicrousdb.php +++ b/ludicrousdb/includes/class-ludicrousdb.php @@ -2133,9 +2133,33 @@ protected function tcp_get_cache_expiration() { return 10; } + /** + * Check if TCP is using a persistent cache or not. + * + * @since 5.1.0 + * + * @return bool True if yes. False if no. + */ + protected function tcp_is_cache_persistent() { + + // Check if using external object cache + if ( wp_using_ext_object_cache() ) { + + // Make sure the global group is added + $this->add_global_group(); + + // Yes + return true; + } + + // No + return false; + } + /** * Get cached up/down value of previous TCP response. - * Look in local cache in case external cache isn't in use or not ready yet. + * + * Falls back to local cache if persistent cache is not available. * * @since 3.0.0 * @@ -2150,19 +2174,25 @@ protected function tcp_cache_get( $key = '' ) { return false; } - // Return cache value if set - if ( isset( $this->tcp_cache[ $key ] ) ) { - return $this->tcp_cache[ $key ]; - } + // Get from persistent cache + if ( $this->tcp_is_cache_persistent() ) { + return wp_cache_get( $key, $this->cache_group ); - // Maybe get from persistent cache - if ( wp_using_ext_object_cache() ) { - $this->add_global_group(); + // Fallback to local cache + } elseif ( ! empty( $this->tcp_cache[ $key ] ) ) { + + // Not expired + if ( ! empty( $this->tcp_cache[ $key ]['expiration'] ) && ( time() < $this->tcp_cache[ $key ]['expiration'] ) ) { - // Set value from persistent - $this->tcp_cache[ $key ] = wp_cache_get( $key, $this->cache_group ); + // Return value or false if empty + return ! empty( $this->tcp_cache[ $key ]['value'] ) + ? $this->tcp_cache[ $key ]['value'] + : false; - return $this->tcp_cache[ $key ]; + // Expired, so delete and proceed + } else { + $this->tcp_cache_delete( $key ); + } } return false; @@ -2170,7 +2200,8 @@ protected function tcp_cache_get( $key = '' ) { /** * Set cached up/down value of current TCP response. - * Store in local cache in case external cache isn't in use or not ready yet. + * + * Falls back to local cache if persistent cache is not available. * * @since 3.0.0 * @@ -2186,14 +2217,49 @@ protected function tcp_cache_set( $key = '', $value = '' ) { return false; } - // Add value to cache - $this->tcp_cache[ $key ] = $value; + // Get expiration + $expires = $this->tcp_get_cache_expiration(); - // Maybe add to persistent cache - if ( wp_using_ext_object_cache() ) { - $this->add_global_group(); + // Add to persistent cache + if ( $this->tcp_is_cache_persistent() ) { + return wp_cache_set( $key, $value, $this->cache_group, $expires ); + + // Fallback to local cache + } else { + $this->tcp_cache[ $key ] = array( + 'value' => $value, + 'expiration' => time() + $expires + ); + } - return wp_cache_set( $key, $value, $this->cache_group, $this->tcp_get_cache_expiration() ); + return true; + } + + /** + * Delete cached up/down value of current TCP response. + * + * Falls back to local cache if persistent cache is not available. + * + * @since 5.1.0 + * + * @param string $key Results of tcp_get_cache_key() + * + * @return bool Results of wp_cache_delete() or true + */ + protected function tcp_cache_delete( $key = '' ) { + + // Bail if invalid key + if ( empty( $key ) ) { + return false; + } + + // Delete from persistent cache + if ( $this->tcp_is_cache_persistent() ) { + return wp_cache_delete( $key, $this->cache_group ); + + // Fallback to local cache + } else { + unset( $this->tcp_cache[ $key ] ); } return true; From 452ddb44e832e5893cfbfae5f5613ff74509b5aa Mon Sep 17 00:00:00 2001 From: John James Jacoby Date: Mon, 26 Oct 2020 09:05:48 -0500 Subject: [PATCH 3/4] Always check TCP responsiveness (if enabled). Even for master. No matter the tries remaining. If TCP responsiveness is being checked and the connection to master goes down, it will exist in the cache. --- ludicrousdb/includes/class-ludicrousdb.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ludicrousdb/includes/class-ludicrousdb.php b/ludicrousdb/includes/class-ludicrousdb.php index 1c23b2f..55b1146 100644 --- a/ludicrousdb/includes/class-ludicrousdb.php +++ b/ludicrousdb/includes/class-ludicrousdb.php @@ -740,13 +740,13 @@ public function db_connect( $query = '' ) { $this->timer_start(); + // Maybe check TCP responsiveness + $tcp = ! empty( $this->check_tcp_responsiveness ) + ? $this->check_tcp_responsiveness( $host, $port, $timeout ) + : null; + // Connect if necessary or possible - $tcp = null; - if ( ! empty( $use_master ) - || empty( $tries_remaining ) - || empty( $this->check_tcp_responsiveness ) - || ( true === $tcp = $this->check_tcp_responsiveness( $host, $port, $timeout ) ) - ) { + if ( ! empty( $use_master ) || empty( $tries_remaining ) || ( true === $tcp ) ) { $this->single_db_connect( $dbhname, $host_and_port, $user, $password ); } else { $this->dbhs[ $dbhname ] = false; From 98a0730949fdfec4ebd595f3ca45d71a0124df79 Mon Sep 17 00:00:00 2001 From: John James Jacoby Date: Wed, 19 Jun 2024 17:24:47 -0500 Subject: [PATCH 4/4] TCP: maybe start object cache Renames some protected methods to better match their intended TCP usages. Because WordPress loads WPDB before the object cache, the persistent TCP cache implementation needs to carefully start the object cache manually. Fixes #126. --- ludicrousdb/includes/class-ludicrousdb.php | 197 +++++++++++++-------- 1 file changed, 125 insertions(+), 72 deletions(-) diff --git a/ludicrousdb/includes/class-ludicrousdb.php b/ludicrousdb/includes/class-ludicrousdb.php index 8006bf9..a1d03d3 100644 --- a/ludicrousdb/includes/class-ludicrousdb.php +++ b/ludicrousdb/includes/class-ludicrousdb.php @@ -136,15 +136,6 @@ class LudicrousDB extends wpdb { */ public $dbhname_heartbeats = array(); - /** - * The number of times to retry reconnecting before dying - * - * @access protected - * @see wpdb::check_connection() - * @var int Default 3. - */ - protected $reconnect_retries = 3; - /** * The tables that have been written to. * @@ -206,18 +197,18 @@ class LudicrousDB extends wpdb { public $default_lag_threshold = null; /** - * In memory cache for tcp connected status. + * Name of object TCP cache group. * - * @var array Default empty array. + * @var string Default 'ludicrousdb'. */ - private $tcp_cache = array(); + public $tcp_cache_group = 'ludicrousdb'; /** - * Name of object cache group. + * In memory cache for TCP connected status. * - * @var string Default 'ludicrousdb'. + * @var array Default empty array. */ - public $cache_group = 'ludicrousdb'; + private $tcp_cache = array(); /** * Whether to ignore replica lag. @@ -240,6 +231,15 @@ class LudicrousDB extends wpdb { */ private $callback_result = null; + /** + * The number of times to retry reconnecting before dying + * + * @access protected + * @see wpdb::check_connection() + * @var int Default 3. + */ + protected $reconnect_retries = 3; + /** * Array of renamed class variables. * @@ -269,31 +269,11 @@ public function __construct( $dbuser = '', $dbpassword = '', $dbname = '', $dbho $this->show_errors(); } - // Bail if first method parameter is empty - if ( empty( $dbuser ) ) { - return; - } - - // Default class vars - $class_vars = array(); - - // Custom class vars via array of arguments - if ( is_array( $dbuser ) ) { - $class_vars = $dbuser; - - // WPDB style parameter pattern - } elseif ( is_string( $dbuser ) ) { - - // Only compact if all params are not empty - if ( ! empty( $dbpassword ) && ! empty( $dbname ) && ! empty( $dbhost ) ) { - $class_vars = compact( $dbuser, $dbpassword, $dbname, $dbhost ); - } - } + // Start the TCP cache + $this->tcp_cache_start(); - // Only set vars if there are vars to set - if ( ! empty( $class_vars ) ) { - $this->set_class_vars( $class_vars ); - } + // Prepare class vars + $this->prepare_class_vars( $dbuser, $dbpassword, $dbname, $dbhost ); } /** @@ -332,6 +312,45 @@ public function __set( $name, $value ) { parent::__set( $name, $value ); } + /** + * Prepare class vars from constructor. + * + * @since 5.2.0 + * + * @param array|string $dbuser New class variables, or Database user. + * @param string $dbpassword Database password. + * @param string $dbname Database name. + * @param string $dbhost Database host. + */ + protected function prepare_class_vars( $dbuser = '', $dbpassword = '', $dbname = '', $dbhost = '' ) { + + // Bail if first method parameter is empty + if ( empty( $dbuser ) ) { + return; + } + + // Default class vars + $class_vars = array(); + + // Custom class vars via array of arguments + if ( is_array( $dbuser ) ) { + $class_vars = $dbuser; + + // WPDB style parameter pattern + } elseif ( is_string( $dbuser ) ) { + + // Only compact if all params are not empty + if ( ! empty( $dbpassword ) && ! empty( $dbname ) && ! empty( $dbhost ) ) { + $class_vars = compact( $dbuser, $dbpassword, $dbname, $dbhost ); + } + } + + // Only set vars if there are vars to set + if ( ! empty( $class_vars ) ) { + $this->set_class_vars( $class_vars ); + } + } + /** * Sets class vars from an array of arguments. * @@ -522,11 +541,20 @@ public function send_reads_to_primaries() { * @param string $group Group, key name in array. * @param array $args Args passed to callback. Default to null. */ - public function run_callbacks( $group, $args = null ) { - if ( ! isset( $this->ludicrous_callbacks[ $group ] ) || ! is_array( $this->ludicrous_callbacks[ $group ] ) ) { + public function run_callbacks( $group = '', $args = null ) { + + // Bail if no callbacks for group + if ( + empty( $group ) + || + ! isset( $this->ludicrous_callbacks[ $group ] ) + || + ! is_array( $this->ludicrous_callbacks[ $group ] ) + ) { return; } + // Prepare args if ( ! isset( $args ) ) { $args = array( &$this ); } elseif ( is_array( $args ) ) { @@ -535,8 +563,13 @@ public function run_callbacks( $group, $args = null ) { $args = array( $args, &$this ); } + // Loop through callbacks foreach ( $this->ludicrous_callbacks[ $group ] as $func ) { + + // Run callback $result = call_user_func_array( $func, $args ); + + // Return result if not null if ( isset( $result ) ) { return $result; } @@ -900,7 +933,13 @@ public function db_connect( $query = '' ) { : null; // Connect if necessary or possible - if ( ! empty( $use_primary ) || empty( $tries_remaining ) || ( true === $tcp ) ) { + if ( + ! empty( $use_primary ) + || + empty( $tries_remaining ) + || + ( true === $tcp ) + ) { $this->single_db_connect( $dbhname, $host_and_port, $user, $password ); } else { $this->dbhs[ $dbhname ] = false; @@ -2317,6 +2356,47 @@ protected function strip_invalid_text_using_db( $to_strip, $charset ) { /** TCP Cache *************************************************************/ + /** + * Start the TCP cache + * + * @since 5.2.0 + * @see https://github.com/stuttter/ludicrousdb/issues/126 + */ + protected function tcp_cache_start() { + static $started = null; + + // Bail if added or caching not available yet + if ( true === $started ) { + return; + } + + // Maybe start object cache + if ( function_exists( 'wp_start_object_cache' ) ) { + wp_start_object_cache(); + + // Make sure the global group is added + $this->tcp_cache_add_global_group(); + } + + // Set started + $started = true; + } + + /** + * Add global TCP cache group. + * + * Only run once, as that is all that is required. + * + * @since 5.2.0 + */ + protected function tcp_cache_add_global_group() { + + // Add the cache group + if ( function_exists( 'wp_cache_add_global_groups' ) ) { + wp_cache_add_global_groups( $this->tcp_cache_group ); + } + } + /** * Get the cache key used for TCP responses * @@ -2354,9 +2434,6 @@ protected function tcp_is_cache_persistent() { // Check if using external object cache if ( wp_using_ext_object_cache() ) { - // Make sure the global group is added - $this->add_global_group(); - // Yes return true; } @@ -2385,7 +2462,7 @@ protected function tcp_cache_get( $key = '' ) { // Get from persistent cache if ( $this->tcp_is_cache_persistent() ) { - return wp_cache_get( $key, $this->cache_group ); + return wp_cache_get( $key, $this->tcp_cache_group ); // Fallback to local cache } elseif ( ! empty( $this->tcp_cache[ $key ] ) ) { @@ -2431,7 +2508,7 @@ protected function tcp_cache_set( $key = '', $value = '' ) { // Add to persistent cache if ( $this->tcp_is_cache_persistent() ) { - return wp_cache_set( $key, $value, $this->cache_group, $expires ); + return wp_cache_set( $key, $value, $this->tcp_cache_group, $expires ); // Fallback to local cache } else { @@ -2464,7 +2541,7 @@ protected function tcp_cache_delete( $key = '' ) { // Delete from persistent cache if ( $this->tcp_is_cache_persistent() ) { - return wp_cache_delete( $key, $this->cache_group ); + return wp_cache_delete( $key, $this->tcp_cache_group ); // Fallback to local cache } else { @@ -2474,30 +2551,6 @@ protected function tcp_cache_delete( $key = '' ) { return true; } - /** - * Add global cache group. - * - * Only run once, as that is all that is required. - * - * @since 4.3.0 - */ - protected function add_global_group() { - static $added = null; - - // Bail if added or caching not available yet - if ( true === $added ) { - return; - } - - // Add the cache group - if ( function_exists( 'wp_cache_add_global_groups' ) ) { - wp_cache_add_global_groups( $this->cache_group ); - } - - // Set added - $added = true; - } - /** * Find a dbh name value for a given $dbh object. *