If $allow_bail is false, the lack of database connection will need
to be handled manually.
public db_connect ( boolean $allow_bail = true ) : boolean | ||
$allow_bail | boolean | Optional. Allows the function to bail. Default true. |
return | boolean | True with a successful connection, false on failure. |
/** * kick() * * Kicks the database to see if the conenction is still alive and if it isn't then tries to reconnect * * @return true if connection alive (may have been reconnected), false otherwise (dead and couldn't be reconnected) * */ public function kick() { // Initialize result to assume failure $result = false; // Use ping to check if server is still present - note will not reconnect automatically for MySQL >= 5.0.13 // and actually we don't want it to as that is bad karma if (!mysql_ping($this->_db->dbh)) { // Database connection appears to have gone away pb_backupbuddy::status(self::STATUS_TYPE_DETAILS, __('Database Server has gone away, attempting to reconnect.', 'it-l10n-backupbuddy')); // Close things down cleanly (from a local perspective) @mysql_close($this->_db->dbh); unset($this->_db->dbh); $this->_db->ready = false; // And attempt to reconnect $this->_db->db_connect(); // Reconnect failed if we have a null resource or ping fails if (NULL == $this->_db->dbh || !mysql_ping($this->_db->dbh)) { // Reconnection failed, make sure user knows pb_backupbuddy::status(self::STATUS_TYPE_DETAILS, __('Database Server reconnection failed.', 'it-l10n-backupbuddy')); // Make sure failure is notified (no need to close things down locally as it's a wrap anyway) $result = false; } else { // Reconnection successful, make sure user knows pb_backupbuddy::status(self::STATUS_TYPE_DETAILS, __('Database Server reconnection successful.', 'it-l10n-backupbuddy')); $result = true; } } else { // Just to let user know that database is still connected pb_backupbuddy::status(self::STATUS_TYPE_DETAILS, __('Database Server connection status verified.', 'it-l10n-backupbuddy')); $result = true; } return $result; }
/** * Figure out which db server should handle the query, and connect to it * * @param string query * * @return resource MySQL database connection */ public function db_connect($query = '') { if (empty($query)) { return false; } $this->last_table = $this->table = $this->get_table_from_query($query); // Use current table with no callback results if (isset($this->ludicrous_tables[$this->table])) { $dataset = $this->ludicrous_tables[$this->table]; $this->callback_result = null; // Run callbacks and either extract or update dataset } else { // Run callbacks and get result $this->callback_result = $this->run_callbacks('dataset', $query); // Set if not null if (!is_null($this->callback_result)) { if (is_array($this->callback_result)) { extract($this->callback_result, EXTR_OVERWRITE); } else { $dataset = $this->callback_result; } } } if (!isset($dataset)) { $dataset = 'global'; } if (empty($dataset)) { return $this->bail("Unable to determine which dataset to query. ({$this->table})"); } else { $this->dataset = $dataset; } $this->run_callbacks('dataset_found', $dataset); if (empty($this->ludicrous_servers)) { if ($this->dbh_type_check($this->dbh)) { return $this->dbh; } 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. $this->dbuser = DB_USER; $this->dbpassword = DB_PASSWORD; $this->dbname = DB_NAME; $this->dbhost = DB_HOST; parent::db_connect(); return $this->dbh; } // Determine whether the query must be sent to the master (a writable server) if (!empty($use_master) || $this->srtm === true || isset($this->srtm[$this->table])) { $use_master = true; } elseif ($is_write = $this->is_write_query($query)) { $use_master = true; if (is_array($this->srtm)) { $this->srtm[$this->table] = true; } // Detect queries that have a join in the srtm array. } elseif (!isset($use_master) && is_array($this->srtm) && !empty($this->srtm)) { $use_master = false; $query_match = substr($query, 0, 1000); foreach ($this->srtm as $key => $value) { if (false !== stripos($query_match, $key)) { $use_master = true; break; } } } else { $use_master = false; } if (!empty($use_master)) { $this->dbhname = $dbhname = $dataset . '__w'; $operation = 'write'; } else { $this->dbhname = $dbhname = $dataset . '__r'; $operation = 'read'; } // Try to reuse an existing connection while (isset($this->dbhs[$dbhname]) && $this->dbh_type_check($this->dbhs[$dbhname])) { // Find the connection for incrementing counters foreach (array_keys($this->db_connections) as $i) { if ($this->db_connections[$i]['dbhname'] == $dbhname) { $conn =& $this->db_connections[$i]; } } if (isset($server['name'])) { $name = $server['name']; // A callback has specified a database name so it's possible the // existing connection selected a different one. if ($name != $this->used_servers[$dbhname]['name']) { if (!$this->select($name, $this->dbhs[$dbhname])) { // this can happen when the user varies and lacks permission on the $name database if (isset($conn['disconnect (select failed)'])) { ++$conn['disconnect (select failed)']; } else { $conn['disconnect (select failed)'] = 1; } $this->disconnect($dbhname); break; } $this->used_servers[$dbhname]['name'] = $name; } } else { $name = $this->used_servers[$dbhname]['name']; } $this->current_host = $this->dbh2host[$dbhname]; // Keep this connection at the top of the stack to prevent disconnecting frequently-used connections if ($k = array_search($dbhname, $this->open_connections)) { unset($this->open_connections[$k]); $this->open_connections[] = $dbhname; } $this->last_used_server = $this->used_servers[$dbhname]; $this->last_connection = compact('dbhname', 'name'); if (!$this->check_connection(false, $this->dbhs[$dbhname])) { if (isset($conn['disconnect (ping failed)'])) { ++$conn['disconnect (ping failed)']; } else { $conn['disconnect (ping failed)'] = 1; } $this->disconnect($dbhname); break; } if (isset($conn['queries'])) { ++$conn['queries']; } else { $conn['queries'] = 1; } return $this->dbhs[$dbhname]; } if (!empty($use_master) && defined("MASTER_DB_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."); } if (empty($this->ludicrous_servers[$dataset][$operation])) { return $this->bail("No databases available with {$this->table} ({$dataset})"); } // Put the groups in order by priority ksort($this->ludicrous_servers[$dataset][$operation]); // 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) { $keys = array_keys($items); shuffle($keys); foreach ($keys as $key) { $servers[] = compact('group', 'key'); } } if (!($tries_remaining = count($servers))) { return $this->bail("No database servers were found to match the query. ({$this->table}, {$dataset})"); } if (!isset($unique_servers)) { $unique_servers = $tries_remaining; } } while ($tries_remaining < $this->reconnect_retries); // Connect to a database server do { $unique_lagged_slaves = array(); $success = false; foreach ($servers as $group_key) { --$tries_remaining; // If all servers are lagged, we need to start ignoring the lag and retry if (count($unique_lagged_slaves) == $unique_servers) { break; } // $group, $key extract($group_key, EXTR_OVERWRITE); // $host, $user, $password, $name, $read, $write [, $lag_threshold, $timeout ] extract($this->ludicrous_servers[$dataset][$operation][$group][$key], EXTR_OVERWRITE); $port = null; // Split host:port into $host and $port if (strpos($host, ':')) { list($host, $port) = explode(':', $host); } // Overlay $server if it was extracted from a callback if (isset($server) && is_array($server)) { extract($server, EXTR_OVERWRITE); } // Split again in case $server had host:port if (strpos($host, ':')) { list($host, $port) = explode(':', $host); } // Make sure there's always a port number if (empty($port)) { $port = 3306; } // Use a default timeout of 200ms if (!isset($timeout)) { $timeout = 0.2; } // Get the minimum group here, in case $server rewrites it if (!isset($min_group) || $min_group > $group) { $min_group = $group; } $host_and_port = "{$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; // Check for a lagged slave, if applicable if (empty($use_master) && empty($write) && !isset($ignore_slave_lag) && isset($this->lag_threshold) && !isset($server['host']) && ($lagged_status = $this->get_lag_cache()) === DB_LAG_BEHIND) { // If it is the last lagged slave and it is with the best preference we will ignore its lag if (!isset($unique_lagged_slaves[$host_and_port]) && $unique_servers == count($unique_lagged_slaves) + 1 && $group == $min_group) { $this->lag_threshold = null; } else { $unique_lagged_slaves[$host_and_port] = $this->lag; continue; } } $this->timer_start(); // 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))) { $this->single_db_connect($dbhname, $host_and_port, $user, $password); } else { $this->dbhs[$dbhname] = false; } $elapsed = $this->timer_stop(); if ($this->dbh_type_check($this->dbhs[$dbhname])) { /** * If we care about lag, disconnect lagged slaves and try to find others. * We don't disconnect if it is the last lagged slave and it is with the best preference. */ if (empty($use_master) && empty($write) && !isset($ignore_slave_lag) && isset($this->lag_threshold) && !isset($server['host']) && $lagged_status !== DB_LAG_OK && ($lagged_status = $this->get_lag()) === DB_LAG_BEHIND && !(!isset($unique_lagged_slaves[$host_and_port]) && $unique_servers == count($unique_lagged_slaves) + 1 && $group == $min_group)) { $unique_lagged_slaves[$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 { $this->set_sql_mode(array(), $this->dbhs[$dbhname]); if ($this->select($name, $this->dbhs[$dbhname])) { $this->current_host = $host_and_port; $this->dbh2host[$dbhname] = $host_and_port; $this->last_connection = compact('dbhname', 'host', 'port', 'user', 'name', 'tcp', 'elapsed', 'success', 'queries', 'lag'); $this->db_connections[] = $this->last_connection; $this->open_connections[] = $dbhname; $success = true; $queries = 1; $lag = isset($this->lag) ? $this->lag : 0; break; } } } $success = false; $this->last_connection = compact('dbhname', 'host', 'port', 'user', 'name', 'tcp', 'elapsed', 'success'); $this->db_connections[] = $this->last_connection; if ($this->dbh_type_check($this->dbhs[$dbhname])) { if (true === $this->use_mysqli) { $error = mysqli_error($this->dbhs[$dbhname]); $errno = mysqli_errno($this->dbhs[$dbhname]); } else { $error = mysql_error($this->dbhs[$dbhname]); $errno = mysql_errno($this->dbhs[$dbhname]); } } $msg = date("Y-m-d H:i:s") . " Can't select {$dbhname} - \n"; $msg .= "'referrer' => '{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}',\n"; $msg .= "'host' => {$host},\n"; if (!empty($error)) { $msg .= "'error' => {$error},\n"; } if (!empty($errno)) { $msg .= "'errno' => {$errno},\n"; } $msg .= "'tcp_responsive' => " . ($tcp === true ? 'true' : $tcp) . ",\n"; $msg .= "'lagged_status' => " . (isset($lagged_status) ? $lagged_status : DB_LAG_UNKNOWN); $this->print_error($msg); } if (empty($success) || !isset($this->dbhs[$dbhname]) || !$this->dbh_type_check($this->dbhs[$dbhname])) { // Lagged slaves were not used. Ignore the lag for this connection attempt and retry. if (!isset($ignore_slave_lag) && count($unique_lagged_slaves)) { $ignore_slave_lag = true; $tries_remaining = count($servers); continue; } $this->run_callbacks('db_connection_error', array('host' => $host, 'port' => $port, 'operation' => $operation, 'table' => $this->table, 'dataset' => $dataset, 'dbhname' => $dbhname)); return $this->bail("Unable to connect to {$host}:{$port} to {$operation} table '{$this->table}' ({$dataset})"); } break; } while (true); if (!isset($charset)) { $charset = null; } if (!isset($collate)) { $collate = null; } $this->set_charset($this->dbhs[$dbhname], $charset, $collate); $this->dbh = $this->dbhs[$dbhname]; // needed by $wpdb->_real_escape() $this->last_used_server = compact('host', 'user', 'name', 'read', 'write'); $this->used_servers[$dbhname] = $this->last_used_server; while (false === $this->persistent && count($this->open_connections) > $this->max_connections) { $oldest_connection = array_shift($this->open_connections); if ($this->dbhs[$oldest_connection] != $this->dbhs[$dbhname]) { $this->disconnect($oldest_connection); } } return $this->dbhs[$dbhname]; }
private function restoreWpdbConnection() { if (!class_exists('wpdb')) { return; } /** @var \wpdb $wpdb */ global $wpdb; if (empty($wpdb)) { $wpdb = new \wpdb(DB_USER, DB_PASSWORD, DB_NAME, DB_HOST); } else { $wpdb->db_connect(); $wpdb->check_connection(); } /** * To avoid `mysqli_free_result` errors during the `wpdb::flush()` method set the result to `null`. * See `wp-db.php` file line 1425. */ $reflection = new \ReflectionClass($wpdb); $resultProperty = $reflection->getProperty('result'); $resultProperty->setAccessible(true); $resultProperty->setValue($wpdb, null); $resultProperty->setAccessible(false); }