diff --git a/ludicrousdb/includes/class-ludicrousdb.php b/ludicrousdb/includes/class-ludicrousdb.php index 9c1ecb3..c125dcd 100644 --- a/ludicrousdb/includes/class-ludicrousdb.php +++ b/ludicrousdb/includes/class-ludicrousdb.php @@ -3,7 +3,7 @@ /** * LudicrousDB Class * - * Disable this check for the file, since we explicitly overwrite default WP db behavior, so this error is always a false positive in this file + * This PHPCS error is always a false positive in this file: * phpcs:disable WordPress.DB.RestrictedFunctions * * @package Plugins/LudicrousDB/Class @@ -20,7 +20,7 @@ class LudicrousDB extends wpdb { /** - * The last table that was queried + * The last table that was queried. * * @var string Default empty string. */ @@ -31,85 +31,96 @@ class LudicrousDB extends wpdb { * is sent and the MySQL result resource stored here. The next query * for FOUND_ROWS() will retrieve this. We do this to prevent any * intervening queries from making FOUND_ROWS() inaccessible. You may - * prevent this by adding "NO_SELECT_FOUND_ROWS" in a comment + * prevent this by adding "NO_SELECT_FOUND_ROWS" in a comment. * * @var resource Default null. */ public $last_found_rows_result = null; /** - * Whether to store queries in an array. Useful for debugging and profiling + * Whether to store queries in an array. Useful for debugging and profiling. * * @var bool Default false. */ public $save_queries = false; /** - * The current MySQL link resource + * Database handle. + * + * The current MySQL link resource. * * @var mysqli|resource|false|null Default null. */ - public $dbh; + public $dbh = null; /** - * Associative array (dbhname => dbh) for established MySQL connections + * Database handles. + * + * Associative array (dbhname => dbh) of established MySQL connections. * * @var array */ public $dbhs = array(); /** - * The multi-dimensional array of datasets and servers + * Database servers. + * + * Multi-dimensional array (dataset => servers) of datasets and servers. * * @var array Default empty array. */ public $ludicrous_servers = array(); /** - * Optional directory of tables and their datasets + * Database tables. + * + * Optional directory of tables and their datasets. * * @var array Default empty array. */ public $ludicrous_tables = array(); /** - * Optional directory of callbacks to determine datasets from queries + * Callbacks. + * + * Optional directory of callbacks to determine datasets from queries. * * @var array Default empty array. */ public $ludicrous_callbacks = array(); /** - * Custom callback to save debug info in $this->queries + * Custom callback to save debug info in $this->queries. * * @var callable Default null. */ public $save_query_callback = null; /** - * Whether to use mysql_pconnect instead of mysql_connect + * Whether to pass "p:" into mysqli_real_connect() to force a + * persistent connection. * * @var bool Default false. */ public $persistent = false; /** - * Allow bail if connection fails + * Kill the application if a database connection fails. * * @var bool Default false. */ - public $allow_bail = false; + public $die_on_disconnect = false; /** * The maximum number of db links to keep open. The least-recently used - * link will be closed when the number of links exceeds this + * link will be closed when the number of links exceeds this. * * @var int Default 10. */ public $max_connections = 10; /** - * Whether to check with fsockopen prior to mysql_connect + * Whether to check with fsockopen prior to mysqli_real_connect. * * @var bool Default true. */ @@ -123,7 +134,21 @@ class LudicrousDB extends wpdb { public $recheck_timeout = 0.1; /** - * Whether to check for heartbeats + * The number of times to retry reconnecting before dying + * + * @var int Default 3. + */ + public $reconnect_retries = 3; + + /** + * The amount of time to wait before trying again to connect to a mysql server. + * + * @var float Default 1. + */ + public $reconnect_sleep = 1.0; + + /** + * Whether to check for heartbeats. * * @var bool Default true. */ @@ -146,28 +171,28 @@ class LudicrousDB extends wpdb { public $send_reads_to_primaries = array(); /** - * The log of db connections made and the time each one took + * The log of db connections made and the time each one took. * * @var array Default empty array. */ public $db_connections = array(); /** - * The list of unclosed connections sorted by LRU + * The list of unclosed connections sorted by LRU. * * @var array Default empty array. */ public $open_connections = array(); /** - * Lookup array (dbhname => host:port) + * Lookup array (dbhname => host:port). * * @var array Default empty array. */ public $dbh2host = array(); /** - * The last server used and the database name selected + * The last server used and the database name selected. * * @var array Default empty array. */ @@ -175,7 +200,7 @@ class LudicrousDB extends wpdb { /** * Lookup array (dbhname => (server, db name) ) for re-selecting the db - * when a link is re-used + * when a link is re-used. * * @var array Default empty array. */ @@ -190,11 +215,18 @@ class LudicrousDB extends wpdb { public $save_backtrace = true; /** - * Maximum lag in seconds. Set null to disable. Requires callbacks + * The default database attributes that are used when * - * @var integer Default null. + * @var array Default database values. */ - public $default_lag_threshold = null; + public $database_defaults = array( + 'dataset' => 'global', + 'write' => 1, + 'read' => 1, + 'timeout' => 0.2, + 'port' => 3306, + 'lag_threshold' => null, + ); /** * Name of object TCP cache group. @@ -203,6 +235,13 @@ class LudicrousDB extends wpdb { */ public $tcp_cache_group = 'ludicrousdb'; + /** + * The amount of time to wait before trying again to ping a server. + * + * @var float Default 0.2 seconds (I.E. 200ms). + */ + public $tcp_timeout = 0.2; + /** * In memory cache for TCP connected status. * @@ -231,15 +270,6 @@ 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. * @@ -250,6 +280,36 @@ class LudicrousDB extends wpdb { private static $renamed_vars = array( 'ignore_slave_lag' => 'ignore_replica_lag', 'srtm' => 'send_reads_to_primaries', + 'allow_bail' => 'die_on_disconnect', + ); + + /** + * Array of binary blob database column types. + * + * @since 5.2.0 + * + * @var array Default array of binary blob column types. + */ + private static $bin_blobs = array( + 'BINARY', + 'VARBINARY', + 'TINYBLOB', + 'MEDIUMBLOB', + 'BLOB', + 'LONGBLOB', + ); + + /** + * Array of allowed character sets. + * + * @since 5.2.0 + * + * @var array Default array of allowed character sets. + */ + private static $allowed_charsets = array( + 'utf8', + 'utf8mb4', + 'latin1', ); /** @@ -369,6 +429,20 @@ protected function set_class_vars( $args = array() ) { $class_vars = get_class_vars( __CLASS__ ); $class_var_keys = array_keys( $class_vars ); + /** + * Explicit backwards compatibility for passing default_lag_threshold + * in as a class argument. + * + * @since 5.2.0 + */ + if ( + isset( $args['default_lag_threshold'] ) + && + ! isset( $args['database_defaults']['lag_threshold'] ) + ) { + $this->database_defaults['lag_threshold'] = $args['default_lag_threshold']; + } + // Loop through class vars and override if set in $args foreach ( $class_var_keys as $var ) { @@ -392,8 +466,6 @@ protected function set_class_vars( $args = array() ) { * Sets $this->charset and $this->collate * * @since 1.0.0 - * - * @global array $wp_global */ public function init_charset() { @@ -428,36 +500,26 @@ public function init_charset() { */ public function add_database( array $db = array() ) { - // Setup some sane default values - $database_defaults = array( - 'dataset' => 'global', - 'write' => 1, - 'read' => 1, - 'timeout' => 0.2, - 'port' => 3306, - 'lag_threshold' => null, - ); - // Merge using defaults - $db = array_merge( $database_defaults, $db ); + $db = array_merge( $this->database_defaults, $db ); // Break these apart to make code easier to understand below $dataset = $db['dataset']; - $read = $db['read']; $write = $db['write']; + $read = $db['read']; // We do not include the dataset in the array. It's used as a key. unset( $db['dataset'] ); - // Maybe add database to array of read's - if ( ! empty( $read ) ) { - $this->ludicrous_servers[ $dataset ]['read'][ $read ][] = $db; - } - // Maybe add database to array of write's if ( ! empty( $write ) ) { $this->ludicrous_servers[ $dataset ]['write'][ $write ][] = $db; } + + // Maybe add database to array of read's + if ( ! empty( $read ) ) { + $this->ludicrous_servers[ $dataset ]['read'][ $read ][] = $db; + } } /** @@ -554,6 +616,17 @@ public function is_debug_display() { ); } + /** + * Are queries being saved? + * + * @since 5.2.0 + */ + public function is_saving_queries() { + return ( + ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) + ); + } + /** * Set a flag to prevent reading from replicas, which might be lagging * after a write. @@ -668,15 +741,26 @@ public function db_connect( $query = '' ) { $this->run_callbacks( 'dataset_found', $dataset ); if ( empty( $this->ludicrous_servers ) ) { + + // Return early dbh if already set if ( $this->dbh_type_check( $this->dbh ) ) { return $this->dbh; } - if ( ! defined( 'DB_HOST' ) || ! defined( 'DB_USER' ) || ! defined( 'DB_PASSWORD' ) || ! defined( 'DB_NAME' ) ) { + // Bail if missing database constants + if ( + ! defined( 'DB_HOST' ) + || + ! defined( 'DB_USER' ) + || + ! defined( 'DB_PASSWORD' ) + || + ! defined( 'DB_NAME' ) + ) { return $this->bail( 'We were unable to query because there was no database defined.' ); } - // Fallback to wpdb db_connect method. + // Fallback to wpdb::db_connect() method. $this->dbuser = DB_USER; $this->dbpassword = DB_PASSWORD; @@ -771,7 +855,7 @@ public function db_connect( $query = '' ) { // A callback specified a database name, but it is possible the // existing connection selected a different one. - if ( $name !== $this->used_servers[ $dbhname ]['name'] ) { + if ( $this->used_servers[ $dbhname ]['name'] !== $name ) { // If the select fails, disconnect and try again if ( ! $this->select( $name, $this->dbhs[ $dbhname ] ) ) { @@ -780,6 +864,7 @@ public function db_connect( $query = '' ) { // permission on the $name database $this->increment_db_connection( $conn, 'disconnect (select failed)' ); $this->disconnect( $dbhname ); + break; } @@ -794,7 +879,8 @@ public function db_connect( $query = '' ) { $this->current_host = $this->dbh2host[ $dbhname ]; - // Keep this connection at the top of the stack to prevent disconnecting frequently-used connections + // Keep this connection at the top of the stack to prevent + // disconnecting from frequently-used connections $key = array_search( $dbhname, $this->open_connections, true ); if ( $key !== false ) { unset( $this->open_connections[ $key ] ); @@ -808,10 +894,11 @@ public function db_connect( $query = '' ) { if ( $this->should_mysql_ping( $dbhname ) && - ! $this->check_connection( false, $this->dbhs[ $dbhname ] ) + ! $this->check_connection( $this->die_on_disconnect, $this->dbhs[ $dbhname ], $query ) ) { $this->increment_db_connection( $conn, 'disconnect (ping failed)' ); $this->disconnect( $dbhname ); + break; } @@ -821,18 +908,25 @@ public function db_connect( $query = '' ) { return $this->dbhs[ $dbhname ]; } - if ( ! empty( $use_primary ) && $this->is_primary_dead() ) { + // Bail if trying to connect to a dead primary + if ( + ! empty( $use_primary ) + && + $this->is_primary_dead() + ) { return $this->bail( 'We are updating the database. Please try back in 5 minutes. If you are posting to your blog please hit the refresh button on your browser in a few minutes to post the data again. It will be posted as soon as the database is back online.' ); } + // Bail if no servers available for table/dataset/operation if ( empty( $this->ludicrous_servers[ $dataset ][ $operation ] ) ) { return $this->bail( "No databases available with {$this->table} ({$dataset})" ); } - // Put the groups in order by priority + // Put the operations in order by key ksort( $this->ludicrous_servers[ $dataset ][ $operation ] ); - // Make a list of at least $this->reconnect_retries connections to try, repeating as necessary. + // Make a list of at least $this->reconnect_retries connections to try, + // repeating as necessary. $servers = array(); do { foreach ( $this->ludicrous_servers[ $dataset ][ $operation ] as $group => $items ) { @@ -846,7 +940,7 @@ public function db_connect( $query = '' ) { } $tries_remaining = count( $servers ); - if ( $tries_remaining === 0 ) { + if ( 0 === $tries_remaining ) { return $this->bail( "No database servers were found to match the query. ({$this->table}, {$dataset})" ); } @@ -872,7 +966,7 @@ public function db_connect( $query = '' ) { $group = $group_key['group']; $key = $group_key['key']; - // $host, $port, $user, $password, $name, $read, $write, $lag_threshold, $timeout ] + // $host, $port, $user, $password, $name, $write, $read, $timeout, $lag_threshold $db_config = $this->ludicrous_servers[ $dataset ][ $operation ][ $group ][ $key ]; $host = $db_config['host']; $port = $db_config['port']; @@ -884,12 +978,7 @@ public function db_connect( $query = '' ) { $timeout = $db_config['timeout']; $lag_threshold = $db_config['lag_threshold']; - // Split host:port into $host and $port - if ( strpos( $host, ':' ) ) { - list( $host, $port ) = explode( ':', $host ); - } - - // Overlay $server if it was extracted from a callback + // Overwrite vars from $server (if it was extracted from a callback) if ( ! empty( $server ) && is_array( $server ) ) { extract( $server, EXTR_OVERWRITE ); @@ -898,19 +987,19 @@ public function db_connect( $query = '' ) { $server = array(); } - // Split again in case $server had host:port + // Maybe split host:port into $host and $port if ( strpos( $host, ':' ) ) { list( $host, $port ) = explode( ':', $host ); } - // Make sure there's always a port number + // Maybe use the default port number (usually: 3306) if ( empty( $port ) ) { - $port = 3306; + $port = (int) $this->database_defaults['port']; } - // Use a default timeout of 200ms + // Maybe use the default timeout (usually: 200ms) if ( ! isset( $timeout ) ) { - $timeout = 0.2; + $timeout = (float) $this->tcp_timeout; } // Get the minimum group here, in case $server rewrites it @@ -918,13 +1007,14 @@ public function db_connect( $query = '' ) { $min_group = $group; } - $host_and_port = "{$host}:{$port}"; + // Format the cache key using the extracted host and port + $host_and_port = $this->tcp_get_cache_key( $host, $port ); // Can be used by the lag callbacks $this->lag_cache_key = $host_and_port; $this->lag_threshold = isset( $lag_threshold ) ? $lag_threshold - : $this->default_lag_threshold; + : $this->database_defaults['lag_threshold']; // Check for a lagged replica, if applicable if ( @@ -941,17 +1031,21 @@ public function db_connect( $query = '' ) { ( $lagged_status = $this->get_lag_cache() ) === DB_LAG_BEHIND // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition ) { - // If it is the last lagged replica and it is with the best preference we will ignore its lag + // If it is the last lagged replica. and it is with the best + // preference, we will ignore its lag if ( ! isset( $unique_lagged_replicas[ $host_and_port ] ) && - ( (int) $this->unique_servers === count( $unique_lagged_replicas ) + 1 ) + ( ( count( $unique_lagged_replicas ) + 1 ) === (int) $this->unique_servers ) && ( $group === $min_group ) ) { $this->lag_threshold = null; + + // Otherwise, log the lag and continue on } else { $unique_lagged_replicas[ $host_and_port ] = $this->lag; + continue; } } @@ -980,28 +1074,42 @@ public function db_connect( $query = '' ) { if ( $this->dbh_type_check( $this->dbhs[ $dbhname ] ) ) { /** - * If we care about lag, disconnect lagged replicas and try to find others. - * We don't disconnect if it is the last lagged replica and it is with the best preference. + * If we care about lag, disconnect lagged replicas and try + * to find others. We don't disconnect if it is the last + * lagged replica and it is with the best preference. */ - if ( empty( $use_primary ) - && empty( $write ) - && empty( $this->ignore_replica_lag ) - && isset( $this->lag_threshold ) - && ! isset( $server['host'] ) - && ( $lagged_status !== DB_LAG_OK ) - && ( $lagged_status = $this->get_lag() ) === DB_LAG_BEHIND // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition - && ! ( + if ( + empty( $use_primary ) + && + empty( $write ) + && + empty( $this->ignore_replica_lag ) + && + isset( $this->lag_threshold ) + && + ! isset( $server['host'] ) + && + ( $lagged_status !== DB_LAG_OK ) + && + ( $lagged_status = $this->get_lag() ) === DB_LAG_BEHIND // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition + && + ! ( ! isset( $unique_lagged_replicas[ $host_and_port ] ) - && ( (int) $this->unique_servers === ( count( $unique_lagged_replicas ) + 1 ) ) - && ( $group === $min_group ) + && + ( (int) $this->unique_servers === ( count( $unique_lagged_replicas ) + 1 ) ) + && + ( $group === $min_group ) ) ) { $unique_lagged_replicas[ $host_and_port ] = $this->lag; $this->disconnect( $dbhname ); + $this->dbhs[ $dbhname ] = false; $success = false; $msg = "Replication lag of {$this->lag}s on {$host_and_port} ({$dbhname})"; + $this->print_error( $msg ); + continue; } else { @@ -1019,6 +1127,7 @@ public function db_connect( $query = '' ) { $this->db_connections[] = $this->last_connection; $this->open_connections[] = $dbhname; $success = true; + break; } } @@ -1061,6 +1170,8 @@ public function db_connect( $query = '' ) { $this->print_error( $msg ); } + // Maybe bail if we have tried all the servers and none of them + // worked. if ( empty( $success ) || @@ -1069,7 +1180,8 @@ public function db_connect( $query = '' ) { ! $this->dbh_type_check( $this->dbhs[ $dbhname ] ) ) { - // Lagged replicas were not used. Ignore the lag for this connection attempt and retry. + // Lagged replicas were not used. Ignore the lag for this + // connection attempt and retry. if ( empty( $this->ignore_replica_lag ) && @@ -1077,9 +1189,11 @@ public function db_connect( $query = '' ) { ) { $this->ignore_replica_lag = true; $tries_remaining = count( $servers ); + continue; } + // Setup the callback data $callback_data = array( 'host' => $host, 'port' => $port, @@ -1100,11 +1214,16 @@ public function db_connect( $query = '' ) { $this->set_charset( $this->dbhs[ $dbhname ] ); $this->dbh = $this->dbhs[ $dbhname ]; // needed by $wpdb->_real_escape() - $this->last_used_server = compact( 'host', 'user', 'name', 'read', 'write' ); + $this->last_used_server = compact( 'host', 'user', 'name', 'write', 'read' ); $this->used_servers[ $dbhname ] = $this->last_used_server; - while ( ( false === $this->persistent ) && count( $this->open_connections ) > $this->max_connections ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found + while ( + ( false === $this->persistent ) + && + ( count( $this->open_connections ) > $this->max_connections ) // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found + ) { $oldest_connection = array_shift( $this->open_connections ); + if ( $this->dbhs[ $oldest_connection ] !== $this->dbhs[ $dbhname ] ) { $this->disconnect( $oldest_connection ); } @@ -1160,10 +1279,14 @@ protected function single_db_connect( $dbhname, $host, $user, $password ) { ? MYSQL_CLIENT_FLAGS : 0; + // Initialize the database handle $this->dbhs[ $dbhname ] = mysqli_init(); - // mysqli_real_connect doesn't support the host param including a port or socket - // like mysql_connect does. This duplicates how mysql_connect detects a port and/or socket file. + /** + * mysqli_real_connect doesn't support the "host" param including a port + * or socket like mysql_connect does. This duplicates how mysql_connect + * detects a port and/or socket file. + */ $port = 0; $socket = ''; $port_or_socket = strstr( $host, ':' ); @@ -1184,18 +1307,35 @@ protected function single_db_connect( $dbhname, $host, $user, $password ) { } } - // Detail found here - https://core.trac.wordpress.org/ticket/31018 - $pre_host = ''; - - // If DB_HOST begins with a 'p:', allow it to be passed to mysqli_real_connect(). - // mysqli supports persistent connections starting with PHP 5.3.0. - if ( ( true === $this->persistent ) && version_compare( phpversion(), '5.3.0', '>=' ) ) { + /** + * If DB_HOST begins with a 'p:', allow it to be passed to + * mysqli_real_connect(). mysqli supports persistent connections + * starting with PHP 5.3.0. + */ + if ( + ( true === $this->persistent ) + && + version_compare( phpversion(), '5.3.0', '>=' ) + ) { $pre_host = 'p:'; - } - - mysqli_real_connect( $this->dbhs[ $dbhname ], $pre_host . $host, $user, $password, '', $port, $socket, $client_flags ); + } else { + $pre_host = ''; + } + + // Connect to the database + mysqli_real_connect( + $this->dbhs[ $dbhname ], + $pre_host . $host, + $user, + $password, + '', + $port, + $socket, + $client_flags + ); - if ( $this->dbhs[ $dbhname ]->connect_errno ) { + // Bail if connection failed + if ( ! empty( $this->dbhs[ $dbhname ]->connect_errno ) ) { $this->dbhs[ $dbhname ] = false; return false; @@ -1300,7 +1440,11 @@ public function select( $db, $dbh_or_table = false ) { protected function load_col_info() { // Bail if not enough info - if ( ! empty( $this->col_info ) || ( false === $this->result ) ) { + if ( + ! empty( $this->col_info ) + || + ( false === $this->result ) + ) { return; } @@ -1349,46 +1493,57 @@ public function _real_escape( $to_escape = '' ) { // phpcs:ignore PSR2.Methods.M * * @since 1.0.0 * - * @param mysqli|resource $dbh The resource given by mysqli_connect + * @param mysqli|resource $dbh The resource given by mysqli_real_connect * @param string $charset Optional. The character set. * @param string $collate Optional. The collation. */ public function set_charset( $dbh, $charset = null, $collate = null ) { + + // Default charset if ( ! isset( $charset ) ) { $charset = $this->charset; } + // Default collation if ( ! isset( $collate ) ) { $collate = $this->collate; } + // Exit if charset or collation are empty if ( empty( $charset ) || empty( $collate ) ) { wp_die( "{$charset} {$collate}" ); } - $allowed = array( 'utf8', 'utf8mb4', 'latin1' ); - - if ( ! in_array( strtolower( $charset ), $allowed, true ) ) { + // Exit if charset is not allowed + if ( ! in_array( strtolower( $charset ), self::$allowed_charsets, true ) ) { wp_die( "{$charset} charset isn't supported in LudicrousDB for security reasons" ); } - if ( ! empty( $charset ) && $this->has_cap( 'collation', $dbh ) ) { - $set_charset_succeeded = true; + // Bail if cannot set collation + if ( ! $this->has_cap( 'collation', $dbh ) ) { + return; + } - if ( $this->has_cap( 'set_charset', $dbh ) ) { - $set_charset_succeeded = mysqli_set_charset( $dbh, $charset ); - } + // Attempt to set the character set + $do_set_names_query = $this->has_cap( 'set_charset', $dbh ) + ? mysqli_set_charset( $dbh, $charset ) + : true; - if ( true === $set_charset_succeeded ) { - $query = $this->prepare( 'SET NAMES %s', $charset ); + // Bail if client charset could not be set + if ( false === $do_set_names_query ) { + return; + } - if ( ! empty( $collate ) ) { - $query .= $this->prepare( ' COLLATE %s', $collate ); - } + // Start the query with charset + $query = $this->prepare( 'SET NAMES %s', $charset ); - $this->_do_query( $query, $dbh ); - } + // Maybe add collation to query + if ( ! empty( $collate ) ) { + $query .= $this->prepare( ' COLLATE %s', $collate ); } + + // Do the query + $this->_do_query( $query, $dbh ); } /** @@ -1400,6 +1555,7 @@ public function set_charset( $dbh, $charset = null, $collate = null ) { */ public function disconnect( $dbhname ) { $key = array_search( $dbhname, $this->open_connections, true ); + if ( $key !== false ) { unset( $this->open_connections[ $key ] ); } @@ -1424,33 +1580,38 @@ public function flush() { /** * Check that the connection to the database is still up. If not, try - * to reconnect + * to reconnect. + * + * This function is called internally by LudicrousDB when a database + * connection * - * If this function is unable to reconnect, it will forcibly die, or if after the - * the template_redirect hook has been fired, return false instead + * If this function is unable to reconnect, it will forcibly die, or if + * after the "template_redirect" hook has been fired, return false instead. * - * If $allow_bail is false, the lack of database connection will need - * to be handled manually + * If $die_on_disconnect is false, the lack of database connection will need + * to be handled manually. * * @since 1.0.0 * - * @param bool $allow_bail Optional. Allows the function to bail. Default true. - * @param bool $dbh_or_table Optional. - * @param string $query Optional. Query string passed db_connect + * @param bool $die_on_disconnect Optional. Allows the function to die. Default true. + * @param bool $dbh_or_table Optional. + * @param string $query Optional. Query string passed db_connect * * @return bool|void True if the connection is up. */ - public function check_connection( $allow_bail = true, $dbh_or_table = false, $query = '' ) { + public function check_connection( $die_on_disconnect = true, $dbh_or_table = false, $query = '' ) { $dbh = $this->get_db_object( $dbh_or_table ); - if ( $this->dbh_type_check( $dbh ) && mysqli_ping( $dbh ) ) { + // Return true if ping is successful. This is the most common case. + if ( + $this->dbh_type_check( $dbh ) + && + mysqli_ping( $dbh ) + ) { return true; } - if ( false === $allow_bail ) { - return false; - } - + // Default to false $error_reporting = false; // Disable warnings, as we don't want to see a multitude of "unable to connect" messages @@ -1459,11 +1620,20 @@ public function check_connection( $allow_bail = true, $dbh_or_table = false, $qu error_reporting( $error_reporting & ~E_WARNING ); } + // Ping failed, so try to reconnect manually for ( $tries = 1; $tries <= $this->reconnect_retries; $tries++ ) { - // On the last try, re-enable warnings. We want to see a single instance of the - // "unable to connect" message on the bail() screen, if it appears. - if ( + // Try to reconnect + $retry = $this->db_connect( $query ); + + // Return true if the connection is up + if ( false !== $retry ) { + return true; + + // On the last try, re-enable warnings. We want to see a single + // instance of the "unable to connect" message on the bail() + // screen, if it appears. + } elseif ( ( $this->reconnect_retries === $tries ) && $this->is_debug() @@ -1471,23 +1641,22 @@ public function check_connection( $allow_bail = true, $dbh_or_table = false, $qu error_reporting( $error_reporting ); } - if ( $this->db_connect( $query ) ) { - if ( $error_reporting ) { - error_reporting( $error_reporting ); - } - - return true; - } + // Sleep before retrying + sleep( $this->reconnect_sleep ); + } - sleep( 1 ); + // Bail here if not allowed to call $this->bail() + if ( false === $die_on_disconnect ) { + return false; } - // If template_redirect has already happened, it's too late for wp_die()/dead_db(). - // Let's just return and hope for the best. + // Bail if template_redirect has already happened, because it's too + // late for wp_die()/dead_db() if ( did_action( 'template_redirect' ) ) { return false; } + // Load translations early so that the error message can be translated wp_load_translations_early(); $message = '