diff --git a/src/wp-includes/wp-db.php b/src/wp-includes/wp-db.php index 1a7ab30266..ffc549cb70 100644 --- a/src/wp-includes/wp-db.php +++ b/src/wp-includes/wp-db.php @@ -157,6 +157,16 @@ class wpdb { */ var $queries; + /** + * The number of times to retry reconnecting before dying. + * + * @since 3.9.0 + * @access protected + * @see wpdb::check_connection() + * @var int + */ + protected $reconnect_retries = 5; + /** * WordPress table prefix * @@ -1192,11 +1202,17 @@ class wpdb { } /** - * Connect to and select database + * Connect to and select database. * * @since 3.0.0 + * + * @param bool $allow_bail Optional. Allows the function to bail, default true. If this is set + * to false, you will need to handle the lack of database connection + * manually. Available since 3.9.0. + * + * @return bool True with a successful connection, false on failure. */ - function db_connect() { + function db_connect( $allow_bail = true ) { $this->is_mysql = true; @@ -1217,7 +1233,7 @@ class wpdb { $this->dbh = @mysql_connect( $this->dbhost, $this->dbuser, $this->dbpassword, $new_link, $client_flags ); } - if ( !$this->dbh ) { + if ( ! $this->dbh && $allow_bail ) { wp_load_translations_early(); // Load custom DB error template, if present. @@ -1237,16 +1253,72 @@ class wpdb {
If you're unsure what these terms mean you should probably contact your host. If you still need help you can always visit the WordPress Support Forums.
" ), htmlspecialchars( $this->dbhost, ENT_QUOTES ) ), 'db_connect_fail' ); - return; + return false; + } else if ( $this->dbh ) { + $this->set_charset( $this->dbh ); + $this->set_sql_mode(); + $this->ready = true; + $this->select( $this->dbname, $this->dbh ); + + return true; } - $this->set_charset( $this->dbh ); + return false; + } - $this->set_sql_mode(); + /** + * Check that the connection to the database is still up. If not, try to reconnect. + * + * If this function is unable to reconnect, it will forcibly die. + * + * @since 3.9.0 + * + * @return bool True if the connection is up. + */ + function check_connection() { + if ( @mysql_ping( $this->dbh ) ) { + return true; + } - $this->ready = true; + $error_reporting = false; - $this->select( $this->dbname, $this->dbh ); + // Disable warnings, as we don't want to see a multitude of "unable to connect" messages + if ( WP_DEBUG ) { + $error_reporting = error_reporting(); + error_reporting( $error_reporting & ~E_WARNING ); + } + + 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 ( $this->reconnect_retries === $tries && WP_DEBUG ) { + error_reporting( $error_reporting ); + } + + if ( $this->db_connect( false ) ) { + if ( $error_reporting ) { + error_reporting( $error_reporting ); + } + + return true; + } + + sleep( 1 ); + } + + // We weren't able to reconnect, so we better bail. + $this->bail( sprintf( ( " +This means that we lost contact with the database server at %s
. This could mean your host's database server is down.
If you're unsure what these terms mean you should probably contact your host. If you still need help you can always visit the WordPress Support Forums.
+" ), htmlspecialchars( $this->dbhost, ENT_QUOTES ) ), 'db_connect_fail' ); + + // Call dead_db() if bail didn't die, because this database is no more. It has ceased to be (at least temporarily). + dead_db(); } /** @@ -1262,6 +1334,7 @@ class wpdb { function query( $query ) { if ( ! $this->ready ) return false; + /** * Filter the database query. * @@ -1281,14 +1354,14 @@ class wpdb { // Keep track of the last query for debug.. $this->last_query = $query; - if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) - $this->timer_start(); + $this->_do_query( $query ); - $this->result = @mysql_query( $query, $this->dbh ); - $this->num_queries++; - - if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) - $this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() ); + // MySQL server has gone away, try to reconnect + if ( empty( $this->dbh ) || 2006 == mysql_errno( $this->dbh ) ) { + if ( $this->check_connection() ) { + $this->_do_query( $query ); + } + } // If there is an error then take note of it.. if ( $this->last_error = mysql_error( $this->dbh ) ) { @@ -1326,6 +1399,29 @@ class wpdb { return $return_val; } + /** + * Internal function to perform the mysql_query call + * + * @since 3.9.0 + * + * @access private + * @see wpdb::query() + * + * @param string $query The query to run + */ + private function _do_query( $query ) { + if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { + $this->timer_start(); + } + + $this->result = @mysql_query( $query, $this->dbh ); + $this->num_queries++; + + if ( defined( 'SAVEQUERIES' ) && SAVEQUERIES ) { + $this->queries[] = array( $query, $this->timer_stop(), $this->get_caller() ); + } + } + /** * Insert a row into a table. * diff --git a/tests/phpunit/tests/db.php b/tests/phpunit/tests/db.php index db4d6a25dc..3ccb5948d7 100644 --- a/tests/phpunit/tests/db.php +++ b/tests/phpunit/tests/db.php @@ -40,6 +40,23 @@ class Tests_DB extends WP_UnitTestCase { return $sql; } + /** + * Test that WPDB will reconnect when the DB link dies + * @ticket 5932 + */ + public function test_db_reconnect() { + global $wpdb; + + $var = $wpdb->get_var( "SELECT ID FROM $wpdb->users LIMIT 1" ); + $this->assertGreaterThan( 0, $var ); + + mysql_close( $wpdb->dbh ); + unset( $wpdb->dbh ); + + $var = $wpdb->get_var( "SELECT ID FROM $wpdb->users LIMIT 1" ); + $this->assertGreaterThan( 0, $var ); + } + /** * Test that floats formatted as "0,700" get sanitized properly by wpdb * @global mixed $wpdb