db_is_active() in Drupal 7 no longer means "can I open a connection" but "have I opened a connection". However, a few places in core still use it for the former logic. Since the database lazy-connects, they should instead catch an exception and do their no-DB logic that way.

http://api.drupal.org/api/function/db_is_active/7

Comments

Crell’s picture

mundanity’s picture

I don't mind taking a stab at this (after #325169: Move error/exception handler higher up in the bootstrap process gets dealt with), but I'm not convinced the solution is to ditch db_is_active() altogether. While I get that try/catch should be utilized for this sort of thing (especially in core), because Drupal doesn't really have a "strict" OO flow, database calls are not well encapsulated. Having a D6 db_is_active() makes sense to me in that light, as it's a bit of a crapshoot to know if any particular API function actually makes a database call or not.

Since there are only four instances of db_is_active() in core, what about:

  1. converting the 4 instances to try/catch
  2. making db_is_active() authoritative for the future availability of the database (to ensure it acts as it did in D6)
  3. adding a comment to db_is_active() that it will likely be deprecated in the "near" future (as in Drupal 8)
Crell’s picture

Here's the problem:

- The DB does not initialize, and therefore is not active, until called. So db_is_active() *should* return false if the connection hasn't been used yet, because it's not active. What you're asking for is a db_is_available().

- I'm fine with a db_is_available(), but the only way to confirm that the connection really is available is to try opening it, which would really just be a try-catch around Database::getConnection(); So there wouldn't be much savings there.

So either way we should convert core, and then we debate whether or not to provide a try-catch wrapper called db_is_available() for people that don't want to add their own try-catch. Mind you, that would also be used in an extremely small number of cases; really, who's going to check for a valid DB every time they use it? They shouldn't.

mundanity’s picture

Right but in D6 the logic is more db_is_available()ish. The four uses of it in core right now pretty much assume that the logic is "will the connection ever be available". My worry would be that contrib code relies on that, although I'd imagine the affected code by the logic change is rather small (but probably quite annoying to debug if it happens).

Or put it this way, since we're removing db_is_active() calls from code, it essentially is there solely for contrib code. We likely can't just remove it this late in the D7 cycle, and as such should it not act more like the D6 version?

Crell’s picture

How many contrib modules even use that function?

I guess my point is that we're in alpha state. If we're going to allow an API change, let's do it right. I don't think "this function is really misnamed and will go away eventually, FYI" counts as "right". "Use an exception like everyone else" or "here's a function with a proper name" would both be "more right".

catch’s picture

The use of db_is_active() in #325169: Move error/exception handler higher up in the bootstrap process nearly caused a 30% regression for cached pages until I spent a decent amount of time benchmarking, then having to prove that the benchmarks weren't flawed, it has no use in its current state, so should be removed.

Crell’s picture

Status: Active » Needs review
StatusFileSize
new5.55 KB

Here's a patch that takes a stab at stabbing db_is_active(). There's still one use of it in _drupal_maintenance_theme() that I'm not sure how to refactor. Someone who actually knows what that function is doing, please jump in here. :-)

It turns out that right now, we're returning NULL for an undefined connection rather than throwing an exception. However, PDO throws an exception when login credentials are invalid. That is inconsistent, and impossible for us to sanely error-handle. Aka, a bug. So this patch also introduces a new exception that gets thrown when an undefined connection is requested, which we then catch in places we used to use db_is_active().

dries’s picture

+++ includes/theme.inc	6 Feb 2010 02:40:01 -0000
@@ -587,16 +587,22 @@ function list_themes($refresh = FALSE) {
+      // If the database is not available, rebiuld the theme data.

Typo: rebiuld should be rebuild.

+++ includes/theme.inc	6 Feb 2010 02:40:01 -0000
@@ -2232,19 +2238,22 @@ function _template_preprocess_default_va
+  catch (DatabaseConnectionNotDefinedException $e) {

What does it mean for a database connection not to be 'defined'. Should it be DatabaseConnectionNotAvailableException instead of DatabaseConnectionNotDefinedException?

Crell’s picture

Bah, typos suck.

A database connection is not defined if the DB layer has not been told that the requested database key exists and what its login credentials are; that's slightly different from it having been told but the credentials being wrong or incomplete (which PDO will already throw an exception for). I'm flexible on the exception name, as long as we don't go bikeshedding it to death.

I'm still looking for someone to explain to me WTF the theme system is doing in the maintenance pathway so we can convert that, too. :-)

catch’s picture

Priority: Normal » Critical

#719850: errors in update.php during bootstrap yield a blank page was duplicate, bumping this to critical based on WSOD over there.

catch’s picture

Status: Needs review » Needs work
andypost’s picture

Subscribe

Crell’s picture

I'd love to drive this home if someone can explain to me what the theme system is doing there so I know how to update it.

andypost’s picture

berdir’s picture

Status: Needs work » Needs review

Ok, here is my try...

- Imho, we need to catch PDOException, not DatabaseNotDefinedExceptions. Because the latter is just thrown when the database is not defined at all.
- Changed DatabaseNotDefinedException to extend from PDOExecption instead of Exception so that it can be catched with PDOException too.
- try/catch doesn't work well for undefined functions/objects so I had to wrap a user_access() and a $user in an if clause
- The code in _drupal_maintenance_theme() does something in case the database is *not* available. I tried to do this by doing a simple, stupid SQL query but that's rather ugly. A better approach might be to simply try go forward and act once an exception happens on itself. But it might be complicated to actually handle it correctly from then on.

Another thing:
- The current "database down" maintenance page simply displays a PDO exception, that's not really nice but not something that needs to be handled here..

berdir’s picture

ups...

karschsp’s picture

I tried applying this patch to fix my "WSOD on update.php" problem and it didn't work. If I go to line 326 of update.php and change ini_set('display_errors', FALSE); to TRUE I get:

Fatal error: Call to undefined function _system_rebuild_theme_data() in /Volumes/Home/Users/stephenkarsch/webs/d7.stevekarsch.com/public_html/includes/theme.inc on line 586

(same error as before I applied the patch).

I will try to dig in but I think I'm in over my head! I'm happy to test though.

andypost’s picture

@Berdir take a look at #719850: errors in update.php during bootstrap yield a blank page

EDIT: _system_rebuild_theme_data() could be unavailable

dries’s picture

Status: Needs review » Reviewed & tested by the community

This patch looks good to be. Would be great to get sign-off from Crell. From the comments above, it sounds as if he'd be in support, but still.

Crell’s picture

Status: Reviewed & tested by the community » Needs work

I'll assume that the maintenance theme chunk makes sense, since I still find most of that to be black magic. :-)

My only other issue here is that I partially disagree with Berdir in #15. DatabaseNotDefinedException is not the same as a PDOException, and should not extend from it. If that means we have two catch statements, so be it. Ain't nothing wrong with having multiple catch statements. :-) If we really can't handle multiple catch statements (and possibly duplicating 3 lines of code), we could always just catch Exception.

Other than that, I think this is good to go.

berdir’s picture

Hm, ok. I just dislike extending all exceptions directly from Exception, but that's not something for this issue :)

Unless we actually do something different in those two catch's, I assume we can just simply catch Exception. Will do a reroll soon...

berdir’s picture

I am working on a re-roll, just a short question...

I found out that the new exception actually can't be thrown at all.

Looking at the following code...

    if (!isset(self::$connections[$key][$target])) {
      // If necessary, a new connection is opened.
      self::$connections[$key][$target] = self::openConnection($key, $target);
    }

    if (empty(self::$connections[$key][$target])) {
      throw new DatabaseConnectionNotDefinedException('The specified database connection is not defined: ' . $key);
    }

The second if is never going to be TRUE, because if the connection isn't set, we enter the first if which then bails out with a Exception in openConnection():

      // If the requested database does not exist then it is an unrecoverable
      // error.
      if (!isset(self::$databaseInfo[$key])) {
        throw new Exception('DB does not exist');
      }

So we can simply drop the conditional stuff and optionally throw the DatabaseConnectionNotDefinedException inside openConnection(). There is also a similiar case, when there is no driver:

      if (!$driver = self::$databaseInfo[$key][$target]['driver']) {
        throw new Exception('Drupal is not set up');
      }

Opinions?

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new7.52 KB

Attached is a re-roll with the proposed changes from above.

This is getting quite a bit off-topic, we should eventually create a follow-up issue, especially if there is a disagreement on how to name the Exceptions or something like that :). There are also notices about undefined array indexes when commenting out the driver in settings.php to test that the exception gets thrown.

Simply doing a catch (Exception $e) now, since we probably don't act differently based on the thrown exception.

qasimzee’s picture

subscribe

catch’s picture

Looks good to me, very small nitpick with:

// user_access()/$user might not be available, default to FALSE.

I had to do a double take because that looked like a path, could we expand it a bit like the comment further down? Apart from that looks RTBC, strongly agree about not bikeshedding exception names in this issue.

Crell’s picture

Status: Needs review » Needs work

Agree with catch on cleaning up that comment, as I don't fully understand it. If a few lines down we try to set it, why do we even need to explain it there? It's just a default value.

Otherwise, Berdir's approach in #23 makes sense to me. Quick reroll and this should be good to go.

berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new7.45 KB

Ok..

I decided to move the user.module stuff out of the try/catch as it is going to fail anyway and added the defaults to the initial defaults array. It does look a bit strange in the patch but I think it's easier to understand when looking at the resulting code.

Comments might still need to be improved though :)

alexanderpas’s picture

+++ includes/database/database.inc
@@ -1628,6 +1627,17 @@ class FieldsOverlapException extends Exception {}
 /**
+ * Exception thrown if an undefined database connection is requested.
+ */
+class DatabaseConnectionNotDefinedException extends Exception {}
+
+/**
+ * Exception thrown if no driver is specified for a database connection.
+ */
+class DatabaseDriverNotSpecifiedException extends Exception {}

These should probaly extend from RuntmeException, just like PDOException
(see also: http://php.net/manual/spl.exceptions.php)
This should allow a more specific catch.

Powered by Dreditor.

berdir’s picture

You are most probably right, but see the middle paragraph in #23, I don't think this is the correct issue for doing that. Instead, we should probably change all Exceptions in database.inc change to extend from RuntimeException.

catch’s picture

Status: Needs review » Reviewed & tested by the community
dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD. Thanks.

andypost’s picture

I see no commit message at http://drupal.org/project/cvs/3060

andypost’s picture

David_Rothstein’s picture

Status: Fixed » Reviewed & tested by the community

Patch still applies :)

andypost’s picture

Status: Reviewed & tested by the community » Needs work

A hunk from _drupal_maintenance_theme() should be if (!class_exists('Database', FALSE)) {

Details in #766100-10: Undefined function _system_rebuild_theme_data when trying to run update.php from D6 -> D7

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new7 KB

Re-rolled minus the theme.maintenance.inc change - we can just do that in #766100: Undefined function _system_rebuild_theme_data when trying to run update.php from D6 -> D7.

Status: Needs review » Needs work

The last submitted patch, die.patch, failed testing.

catch’s picture

Status: Needs work » Needs review

#36: die.patch queued for re-testing.

catch’s picture

Status: Needs review » Reviewed & tested by the community

Since this is just a re-roll without the hunk that was committed elsewhere, and was already RTBC before apart from that hunk, I'm setting it back.

berdir’s picture

#36: die.patch queued for re-testing.

gustavb’s picture

Another nitpick,

       if (!$driver = self::$databaseInfo[$key][$target]['driver']) {
-        throw new Exception('Drupal is not set up');
+        Throw new DatabaseDriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
       }

it should be throw new DatabaseDriverNotSpecifiedException..., right?

andypost’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new7.48 KB

Reroll with changes:
1) "Throw" => throw
2) in list_themes() try-catch moved inside if() because only system_list() depends on DB

Crell’s picture

Status: Needs review » Reviewed & tested by the community

mfb has another issue that's dependent on this one. Let's resolve it and move on to a new era of errors. Or something.

mfb’s picture

Status: Reviewed & tested by the community » Needs work

One problem, there's still a call to db_is_active() in theme.maintenance.inc

andypost’s picture

Status: Needs work » Reviewed & tested by the community
StatusFileSize
new7.48 KB
mfb’s picture

Status: Reviewed & tested by the community » Needs work

There's still a call to db_is_active() in theme.maintenance.inc -- but this patch removes the function.

mfb’s picture

Status: Needs work » Reviewed & tested by the community

Apparently my cvs is on crack. I mean, cvs update -D does bad things. RTBC

(sorry :)

webchick’s picture

Priority: Critical » Normal
Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs documentation

It looks like Dries meant to commit this earlier and missed it. Oops!

Committed to HEAD.

Looks like we need to document this change in the 6 => 7 module upgrade docs.

andypost’s picture

Added placeholder http://drupal.org/update/modules/6/7#db_is_active
Needs explain better when use class_exists() [bootstrap] and when exception catching [after bootstrap]

Crell’s picture

Does this really need a separate updating line item when we already have an item for "everything about the DB changed, here's the docs, go read 'em"? Did anyone outside of core even USE db_is_active()? :-) I really don't think we need a second line item here.

andypost’s picture

Crell I've used this once in my custom module so I think this needs details mostly about replacing within bootstrap.

sivaji_ganesh_jojodae’s picture

#779494: Notices on install screens is probably related to this.

Crell’s picture

Status: Needs work » Fixed

I updated the module update page.

Status: Fixed » Closed (fixed)
Issue tags: -Needs documentation

Automatically closed -- issue fixed for 2 weeks with no activity.