Index: includes/database.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/Attic/database.inc,v
retrieving revision 1.92.2.5
diff -u -F^[fc] -r1.92.2.5 database.inc
--- includes/database.inc	9 Jun 2009 10:42:02 -0000	1.92.2.5
+++ includes/database.inc	30 Jul 2009 00:06:01 -0000
@@ -130,6 +130,13 @@ function db_set_active($name = 'default'
     install_goto('install.php');
   }
 
+  // If an array of possible databases (e.g., for a master-master setup) was
+  // specified and this is the first time we're trying to connect to them,
+  // choose the one we will connect to during this page request.
+  if (!isset($db_conns[$name]) && isset($db_url[$name]) && is_array($db_url[$name])) {
+    $db_conns[$name] = db_choose_active($db_url[$name], $name);
+  }
+
   if (!isset($db_conns[$name])) {
     // Initiate a new connection, using the named DB URL specified.
     if (is_array($db_url)) {
@@ -149,7 +156,15 @@ function db_set_active($name = 'default'
       _db_error_page("The database type '". $db_type ."' is unsupported. Please use either 'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
     }
 
+    try {
+      $db_conns[$name] = db_connect($connect_url);
+    }
+    catch (Exception $e) {
+      _db_error_page($e->getMessage());
+    }
+    /* Original version (replaced with exception-based method above):
     $db_conns[$name] = db_connect($connect_url);
+    */
   }
 
   $previous_name = $active_name;
@@ -161,6 +176,96 @@ function db_set_active($name = 'default'
 }
 
 /**
+ * Choose a database server to connect to during the current page request.
+ *
+ * This function will loop through a list of provided databases and attempt to
+ * connect to them in order, stopping when a successful connection is made.
+ * However, it also checks a (pluggable) cache to see if a different database
+ * server is preferred and moves that server to the front of the list so that
+ * it can be attempted before any of the others. If the database server that
+ * is eventually connected to is different from the one stored in the cache,
+ * it writes the new, preferred sever to the cache before exiting.
+ *
+ * @param $db_urls
+ *   An ordered array of possible database servers to connect to. The keys
+ *   should be unique identifiers for each server (e.g., "db1", "db2"), and
+ *   the values should be database connection URLs. Once a database server
+ *   has been chosen, the array will be replaced with a string containing the
+ *   database connection URL that is actually being used.
+ * @param $name
+ *   The name of the database that is being connected to. Usually this will be
+ *   'default', but alternate values can be specified if a separate, external
+ *   set of database servers is being used for part of the page request (in
+ *   addition to the main Drupal database).
+ * @return
+ *   The new database connection.
+ */
+function db_choose_active(&$db_urls, $name) {
+  // Load the pluggable cache and check if it contains the name of a preferred
+  // database server. If so, move that server to the front of the list.
+  include_once variable_get('db_server_cache_inc', 'includes/db.server.cache.filesystem.inc');
+  if (function_exists('db_server_cache_get') && ($cached_db_server_id = db_server_cache_get($name))) {
+    if (isset($db_urls[$cached_db_server_id]) && $db_urls[$cached_db_server_id] != reset($db_urls)) {
+      $db_urls = array($cached_db_server_id => $db_urls[$cached_db_server_id]) + $db_urls;
+    }
+  }
+
+  // Loop through each database server and try to connect to it. Stop once a
+  // successful connection is made.
+  $error_message = '';
+  foreach ($db_urls as $id => $connect_url) {
+    $db_type = substr($connect_url, 0, strpos($connect_url, '://'));
+    $handler = "./includes/database.$db_type.inc";
+    if (is_file($handler)) {
+      include_once $handler;
+      // Try to connect to each server three times, with an increasing delay
+      // between each attempt.
+      $connection_attempt = 1;
+      $max_connection_attempts = 3;
+      while ($connection_attempt <= $max_connection_attempts) {
+        try {
+          // Quit the entire loop as soon as a successful connection is made.
+          $connection = db_connect($connect_url);
+          $preferred_db_server_id = $id;
+          break 2;
+        }
+        catch (Exception $e) {
+          if ($connection_attempt == $max_connection_attempts) {
+            $error_message .= ' Server '. $id .': '. $e->getMessage();
+          }
+          else {
+            // Wait 0.5 seconds before the second attempt, and 1 second before
+            // the third attempt.
+            usleep($connection_attempt * 500000);
+          }
+        }
+        $connection_attempt += 1;
+      }
+    }
+    else {
+      $error_message .= ' Server '. $id .": The database type '". $db_type ."' is unsupported. Please use either 'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.";
+    }
+  }
+
+  // If no connection was made, show an error page and exit.
+  if (empty($connection)) {
+    _db_error_page($error_message);
+  }
+
+  // If the preferred database server changed, write this to the cache.
+  if (empty($cached_db_server_id) || $cached_db_server_id != $preferred_db_server_id) {
+    if (function_exists('db_server_cache_set')) {
+      db_server_cache_set($name, $preferred_db_server_id);
+    }
+  }
+
+  // Replace the list of database connection URLs with the one that is
+  // actually being used, and return the new database connection.
+  $db_urls = $db_urls[$preferred_db_server_id];
+  return $connection;
+}
+
+/**
  * Helper function to show fatal database errors.
  *
  * Prints a themed maintenance page with the 'Site off-line' text,
Index: includes/database.mysql.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/Attic/database.mysql.inc,v
retrieving revision 1.89.2.1
diff -u -F^[fc] -r1.89.2.1 database.mysql.inc
--- includes/database.mysql.inc	21 Jul 2009 08:52:29 -0000	1.89.2.1
+++ includes/database.mysql.inc	30 Jul 2009 00:06:01 -0000
@@ -53,7 +53,10 @@ function db_connect($url) {
 
   // Check if MySQL support is present in PHP
   if (!function_exists('mysql_connect')) {
+    throw new Exception('Unable to use the MySQL database because the MySQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+    /* Original version (replaced with exception-based method above):
     _db_error_page('Unable to use the MySQL database because the MySQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+    */
   }
 
   // Decode url-encoded information in the db connection string
@@ -76,8 +79,11 @@ function db_connect($url) {
   //   (matched) rows, not the number of affected rows.
   $connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2);
   if (!$connection || !mysql_select_db(substr($url['path'], 1))) {
+    throw new Exception(mysql_error());
+    /* Original version (replaced with exception-based method above):
     // Show error screen otherwise
     _db_error_page(mysql_error());
+    */
   }
 
   // Force UTF-8.
Index: includes/database.mysqli.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/Attic/database.mysqli.inc,v
retrieving revision 1.54.2.1
diff -u -F^[fc] -r1.54.2.1 database.mysqli.inc
--- includes/database.mysqli.inc	21 Jul 2009 08:52:30 -0000	1.54.2.1
+++ includes/database.mysqli.inc	30 Jul 2009 00:06:02 -0000
@@ -57,7 +57,10 @@ function db_version() {
 function db_connect($url) {
   // Check if MySQLi support is present in PHP
   if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
+    throw new Exception('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+    /* Original version (replaced with exception-based method above):
     _db_error_page('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+    */
   }
 
   $url = parse_url($url);
@@ -76,7 +79,10 @@ function db_connect($url) {
   @mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);
 
   if (mysqli_connect_errno() > 0) {
+    throw new Exception(mysqli_connect_error());
+    /* Original version (replaced with exception-based method above):
     _db_error_page(mysqli_connect_error());
+    */
   }
 
   // Force UTF-8.
Index: includes/database.pgsql.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/Attic/database.pgsql.inc,v
retrieving revision 1.68.2.5
diff -u -F^[fc] -r1.68.2.5 database.pgsql.inc
--- includes/database.pgsql.inc	9 Jun 2009 10:53:52 -0000	1.68.2.5
+++ includes/database.pgsql.inc	30 Jul 2009 00:06:02 -0000
@@ -47,7 +47,10 @@ function db_version() {
 function db_connect($url) {
   // Check if PostgreSQL support is present in PHP
   if (!function_exists('pg_connect')) {
+    throw new Exception('Unable to use the PostgreSQL database because the PostgreSQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+    /* Original version (replaced with exception-based method above):
     _db_error_page('Unable to use the PostgreSQL database because the PostgreSQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
+    */
   }
 
   $url = parse_url($url);
@@ -79,7 +82,12 @@ function db_connect($url) {
   $connection = @pg_connect($conn_string);
   if (!$connection) {
     require_once './includes/unicode.inc';
+    $error_message = decode_entities($php_errormsg);
+    ini_set('track_errors', $track_errors_previous);
+    throw new Exception($error_message);
+    /* Original version (replaced with exception-based method above):
     _db_error_page(decode_entities($php_errormsg));
+    */
   }
 
   // Restore error tracking setting
Index: includes/db.server.cache.filesystem.inc
===================================================================
RCS file: includes/db.server.cache.filesystem.inc
diff -N includes/db.server.cache.filesystem.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/db.server.cache.filesystem.inc	30 Jul 2009 00:06:02 -0000
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Pluggable interface for storing preferred database servers in a temporary
+ * filesystem cache.
+ */
+
+/**
+ * Retrieve the ID of a preferred database server from the cache.
+ *
+ * @param $name
+ *   The name of the database that is being connected to. Usually this will be
+ *   'default', but alternate values can be specified if a separate, external
+ *   set of database servers is being used for part of the page request (in
+ *   addition to the main Drupal database).
+ * @return
+ *   The ID of the preferred server for this database, or FALSE if none is
+ *   found in the cache.
+ */
+function db_server_cache_get($name) {
+  $filename = _db_server_cache_filename($name);
+  return (!empty($filename) && is_file($filename)) ? file_get_contents($filename) : FALSE;
+}
+
+/**
+ * Store the ID of a preferred database server in the cache.
+ *
+ * @param $name
+ *   The name of the database that is being connected to. Usually this will be
+ *   'default', but alternate values can be specified if a separate, external
+ *   set of database servers is being used for part of the page request (in
+ *   addition to the main Drupal database).
+ * @param $db_server_id
+ *   The ID of the preferred server for this database that should be stored in
+ *   the cache.
+ */
+function db_server_cache_set($name, $db_server_id) {
+  $filename = _db_server_cache_filename($name);
+  if (!empty($filename)) {
+    file_put_contents($filename, $db_server_id);
+  }
+}
+
+/**
+ * Helper function to determine and validate the name of the file in which the
+ * cached data will be stored.
+ */
+function _db_server_cache_filename($name) {
+  include_once 'includes/file.inc';
+  return file_directory_path() .'/db.server.cache.'. $name;
+}
