In D7, we started using t() in our error handler. D6 did not do this. Unfortunately, t() uses theme('placeholder') which in turn initializes the theme system if it hasn't initialized already. It is quite possible to trigger a handled error before all the modules are loaded. When this happens, we rebuild the theme registry (if needed) and get quite a tiny, useless theme registry in our cache. This obviously kills the presentation of all future page views served with this theme registry.

I stumbled into this with drush, which triggers an expected 'already sent headers' warning when we emit our http header during full bootstrap. The attached patch removes t() from drupal_error_handler().

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Status: Needs review » Needs work

The last submitted patch failed testing.

moshe weitzman’s picture

Component: base system » theme system
Assigned: Unassigned » moshe weitzman
Status: Needs work » Needs review
FileSize
1.3 KB

This one goes more to the heart of the problem. Don't ever save the theme registry until we loaded all modules.

chx’s picture

Instead of a separate function what about adding memory to module_load_all? http://drupalbin.com/11638

chx’s picture

Or instead you want a static $full and flip it to TRUE when $bootstrap is FALSE and return that. Works as well and returns less weird.

Dries’s picture

Certainly needs code comments, regardless of the solution we chose to go with.

I'm in favor of #2 as it provides a re-usable API.

Should it be when all modules are loaded or when Drupal is fully bootstrapped?

moshe weitzman’s picture

FileSize
1.45 KB

I'm in favor of #2 as well. I added some comments and rerolled.

For most purposes, module load complete is all that matters. Those are the files which can implement hooks and provide registry like info. I'm not opposed to checking for bootstrap complete, but I don't see much benefit either.

moshe weitzman’s picture

FileSize
1.65 KB

chx prefers to keep all the logic in module_load_all() so here is an alternative that does that.

moshe weitzman’s picture

FileSize
1.65 KB

oops - wrong patch.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

I think Dries can now pick between #6 and #8.

Edit: Actually, if you want #6, can you replace system_menu with node_menu? We do an explicit load of system.module in installer so we could get false positive.

Dries’s picture

The delta between all modules being loaded and the end of the full-bootstrap is pretty small. The only thing that happens in between is (i) setting a custom theme if any and (ii) calling hook_init(). If we don't want to introduce a new function, we could also use (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL).

moshe weitzman’s picture

FileSize
1.41 KB

Thats excellent. Patch attached. I added a little doxy to drupal_bootstrap().

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD.

moshe weitzman’s picture

Title: Don't initialize theme system before modules are included » Don't save theme registry before modules are included

Change title for posterity

chx’s picture

Status: Fixed » Active

So now using theme() in hook_init is a performance blow?

moshe weitzman’s picture

Hmmm. Thats true. Maybe we should go back to a prior iteration. Or move hook_init() to index.php, right before execute_active_handler()

moshe weitzman’s picture

Status: Active » Needs review
FileSize
1.21 KB

OK, back to #6, using node_menu instead of system_menu.

chx’s picture

Status: Needs review » Reviewed & tested by the community

I still do not get why we do not want to ask module_load_all whether it loaded all but if we do not want this is a lot better.

moshe weitzman’s picture

I chose not to do that because it is a small WTF to have a function return a boolean in some cases and in other cases, load files and then return nothing. True, we are choosing between small WTF here. Thanks for the RTBC.

sun’s picture

Status: Reviewed & tested by the community » Needs review
 /**
+ * Determines whether we have passed the module_load_all() stage of full 
+ * bootstrap.
+ * 
+ * @see _theme_load_registry()
+ *
+ * @return boolean
+ */

1) Function summary needs to be on 1 line.

2) @return should come before @see

+function module_is_loaded_all() {
+  // Look for an arbitrary function within a required module.
+  return function_exists('node_menu');
+}

3) I agree with chx - I just need a require_once modules/node/node.module to break this.

sun’s picture

moshe mentioned an earlier patch as alternative:

 function module_load_all($bootstrap = FALSE) {
-  foreach (module_list(TRUE, $bootstrap) as $module) {
-    drupal_load('module', $module);
+  static $has_run = FALSE;
+  
+  if (is_null($bootstrap)) {
+    return $has_run;
+  }
+  else {
+    $has_run = TRUE;
+    foreach (module_list(TRUE, $bootstrap) as $module) {
+      drupal_load('module', $module);
+    }
   }
 }

a) This would work, if $has_run is only set when $bootstrap is FALSE. Otherwise, we still don't have the full set of modules.

b) I wonder whether using a drupal_static() here would be acceptable.

moshe weitzman’s picture

FileSize
1.75 KB

OK, Back to the chx approach. Not using drupal_static as there is no benefit. You can't uninclude files in php.

sun’s picture

uhm, module_load_all() now seems to have a completely broken indentation. :-/

I know that code can't be unloaded. I specifically questioned the drupal_static() to perhaps decrease the WTF-factor:

function module_load_all($bootstrap = FALSE) {
  $has_run = drupal_static(__FUNCTION__, FALSE);
...
function _theme_load_registry() {
...
   if (drupal_static('module_load_all', FALSE)) {
moshe weitzman’s picture

FileSize
1.62 KB

fixed indentation

thats another option, with other WTF. Sticking with #21 approach for now.

chx’s picture

Hm, lots of whitespace errors and lots of unnecessary code too.

sun’s picture

Status: Needs review » Reviewed & tested by the community

Much better now. :)

Dries’s picture

Status: Reviewed & tested by the community » Needs work

module_load_all(NULL) is not documented? specifically having to pass NULL seems like a WTF to me.

moshe weitzman’s picture

It is documented. See the @return for module_load_all(). We could restate this if anyone has ideas to improve clarity.

And yes, it is a small WTF. We have tried all the various permutations here. They all have small WTF to them. IMO whats needed to fix this properly is to add a small drupal_set_page_context() and drupal_get_page_context() or just rename the drupal_static system like that. Then I would do a drupal_set_page_context('module_loaded', TRUE) from within module_load_all() and then check drupal_get_page_context('module_loaded') when saving theme registry. This is similar to tha_sun's suggestion. If Dries wants that, I am happy to provide it immediately.

Otherwise, please it would be ideal if Dries' provides some direction. The alternative approach is #16.

sun’s picture

catch’s picture

Status: Needs work » Needs review

We have a choice here, nothing specifically needing work.

Dries’s picture

In #14, @chx wrote "So now using theme() in hook_init is a performance blow?" but unlike him and Moshe, I don't see how that is the case. The big thing the previous patch did, was conditionally store the registry. I'd think that 99% of the time, _theme_load_registry() would succeed in getting a copy of the registry from the database cache. @chx seems to suggest, but I could be wrong given the briefness of his comment in #14, that loading the registry from the cache might result in many cache misses.

moshe weitzman’s picture

@Dries is right. We are really only fixing the 1% of the time when there is no cached registry. In that case, every theme() call during hook_init() will rebuild the full theme registry. I think thats a problem worth solving, #24 is our best effort IMO.

Dries’s picture

The trade-off is: do we want to fix a rare performance regression at the cost of making our API more ugly. Performance vs DX.

For me, the big question is: is it 1% or is it 0.01% or is it 5%? It feels like it is closer to 0.01% than 1% so my personal preference would be to suck up the small performance regression in favor of a cleaner API. That said, I haven't benchmark the miss/hit rate of the theme registry cache.

alpritt’s picture

aha, please read especially comment #5 and #7 from #600422: WSOD if theme() called before bootstrap completes.

moshe weitzman’s picture

All true. On most sites it will be .01% of requests. The problem is that we still cache_clear_all() rather frequently, and that includes the theme registry. So when this does happen, it happens at the very worst time. At least theme registry rebuild taxes the web server and not the DB server ... More granular cache clearing has to be an important goal for D8.

I propose that we use the term 'DX' to refer to ugliness in our APIs. The patch here is isolated to Drupal guts. No contrib developer would ever care. So what we have here is code ugliness, not DX. I think there is a difference.

IMO, we should fix this before release, but it isn't the most important issue right now.

Damien Tournoud’s picture

Status: Needs review » Fixed

Running theme() in hook_init() is not supported *at all* (it triggers the initialization of the theme system, which breaks about everything from the administration theme to the block configuration page). Fixing that in not in the scope of this issue.

moshe weitzman’s picture

Well, if that is so then theme() needs to give a fatal error if you try. We can't have these secret traps.

Damien Tournoud’s picture

@Moshe: we have had that secret trap for years now. I agree that's not nice, but this is really not in the scope of this issue.

moshe weitzman’s picture

Status: Fixed » Reviewed & tested by the community

The whole point of inventing hook_boot in D6 was so that developers could know that hook_init() is working with a fully bootstrapped drupal and can do whatever it wants. The secret trap is a bug. This patch is one step toward fixing that bug and keeping up performance during cache flushes. I disagree with marking this fixed because other bugs exist. We won't make good progress that way.

Damien Tournoud’s picture

Status: Reviewed & tested by the community » Fixed
sun’s picture

I must really wonder since when we simply ignore race conditions like this, especially, since we already know that the introduced helper function here is required for another issue. (#591794: Allow themes to alter forms and page)

Damien Tournoud’s picture

Status: Fixed » Needs review

The previous status change was a cross-post, sorry about that. I think the correct status at this point is CNR.

mattyoung’s picture

#35

>Running theme() in hook_init() is not supported *at all* (it triggers the initialization of the theme system, which breaks about everything from the administration theme to the block configuration page). Fixing that in not in the scope of this issue.

also breaks maintenance page theming: http://drupal.org/node/374645#comment-1262479

the theme system cannot be touched in hook_init() and a whole lot of things can indirectly cause the theme system to be init'ed like getting a form, calling t(). A lot of people get blank maintenance page and it's always some module's hook_init() touching the theme system some how.

This is really bad and there is no warning of this in the API doc: http://api.drupal.org/api/function/hook_init/6

init_theme() should call drupal_get_bootstrap_phase() and blow itself up if it's before hook_init()!

#405578: Need to prohibit hook_init() from initializing theme system

David_Rothstein’s picture

Running theme() in hook_init() is not supported *at all* (it triggers the initialization of the theme system, which breaks about everything from the administration theme to the block configuration page). Fixing that in not in the scope of this issue.

This is no longer true in Drupal 7, due to #553944: Define hook_menu_get_item_alter() as a reliable hook that runs before the page is doomed. Running theme() in hook_init() works perfectly well... Or rather, it works perfectly well when the patch in #24 is applied, because otherwise, this issue causes a WSOD in those cases :)

I just spent quite a while tracking that down while working on a module for Drupal 7... because it's not even a "real" WSOD, really just literally a blank page.

I agree with Moshe - it is reasonable to tell people they can't do certain things in hook_boot(), but not hook_init() - people should be able to do whatever they want there. We should fix that here, and probably add a test to make sure that this bug doesn't crop up again.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

Back to RTBC for #24. We have no perfect solution here. I replied to Dries' #32 in #34.

effulgentsia’s picture

subscribing

webchick’s picture

Status: Reviewed & tested by the community » Fixed

Ok, moshe's response seems reasonable. Agreed that this is an internal change, rather than a developer-facing one, and that being able to use the full range of API functions in hook_init() is expected behaviour in terms of DX.

Committed #24 to HEAD.

David_Rothstein’s picture

Status: Fixed » Needs review
FileSize
3.44 KB

As per above, we really need a test for this... The attached test correctly fails when the patch in #24 is reverse applied, but passes with the current D7 codebase.

moshe weitzman’s picture

Actually, this fix is still incomplete. Now we don't persist the theme registry which is good but we don't handle the theme call that kicked off this badness. It just fails since there is no theme registry. We need to pass back our temporary registry to theme(). I don't think David's test is quite right. Let me see if I can get it to fail and then we can fix the bug for sure.

effulgentsia’s picture

Just want to give another plug for #620452: Optimize drupal_static(): we shouldn't sacrifice simpletest coverage for performance and vice versa, which would let us keep the registry in a resettable static and let theme() access it quickly, instead of maintaining its own copy that needs resetting.

David_Rothstein’s picture

Now we don't persist the theme registry which is good but we don't handle the theme call that kicked off this badness. It just fails since there is no theme registry.

Well, if someone is calling theme() that early - for example, in hook_boot() - we want it to fail somehow, I think, because otherwise the theme system gets initialized too early and we aren't even necessarily using the correct theme for that page.

However, I guess the current situation is a little weird, where in those cases it returns blank page output (rather than throwing an exception or something) on page requests where it is trying to populate the registry, but doesn't cause any direct error (?) on other page requests.

sun.core’s picture

aspilicious’s picture

moshe weitzman’s picture

FYI, the patch thats pending here is just adding a test. There is a critical bug described in #48 thats needs resolving

Status: Needs review » Needs work

The last submitted patch, theme-module-bootstrap-test-592008-47.patch, failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
519 bytes

Lets attack this languishing critical.

Here is a simple patch that rejects early theme() calls. Lets see what bot thinks.

David_Rothstein’s picture

Seems pretty reasonable. Shouldn't we include something like my test from #47 in here? That's complementary since it tests that the theme system does work immediately after this stage - and which definitely didn't work correctly in Drupal 6.

Note that Syslog module has some weird issues with theme() getting called too early in the page request; it has been nicely filling up the error logs in Drupal Gardens. See: #319844-17: Watchdog should work, even if the error is too early, before module.inc is loaded. It shouldn't affect this patch one way or the other since in that case theme.inc isn't even loaded yet - just thought it was an interesting related point to mention :)

moshe weitzman’s picture

FileSize
3.71 KB

Here it is, with David's test (slightly updated since theme_placeholder was removed).

catch’s picture

Status: Needs review » Needs work

Patch looks sane enough to me, minor whitespace issue:

+}
+
+
moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
3.71 KB

Without extra line

catch’s picture

Status: Needs review » Reviewed & tested by the community

Patch looks good, thanks for putting the module_load_all() check before the !defined(), comes with tests, rtbc.

webchick’s picture

Status: Reviewed & tested by the community » Needs work

Could we just get a little one-or-two-liner comment above that new theme() chunk to explain how that can happen? I feel like there's information buried in this issue that isn't making its way to the code. (Also module_load_all(NULL) makes you go ???)

catch’s picture

module_load_all(NULL) is not invented here. I'm not sure what the conditions are which trigger this any more, after t() being fixed, so will leave that to those who are.

webchick’s picture

Right, no, I realize that. I just meant a comment would help soften the WTF blow. ;)

David_Rothstein’s picture

Status: Needs work » Needs review
FileSize
4.42 KB

Here's an attempt at a code comment. I'm not sure we need to identify a specific scenario where this can happen (any time before all modules are loaded can encompass a lot of things in theory) but I did try to add a comment explaining why we need to bail out in this case.

catch’s picture

Status: Needs review » Reviewed & tested by the community

Works for me.

Dries’s picture

I looked at the test, and it looks a bit weird -- it doesn't seem to test the bug. Instead, it seems to test hook-init?

David_Rothstein’s picture

Yes, we are testing the fact that the theme system actually does work at one of the earliest points in the bootstrap cycle that we expect it to. That didn't work in Drupal 6, and didn't work for a while in Drupal 7 - but was allowed to start working when one of the previous patches in this issue was committed (and possibly it was also one of the patches before that in this issue that had broken it for D7 - can't remember). We need that test :)

I guess that in addition there could be a test to ensure that the theme system doesn't work earlier than that - i.e. that calling theme() in hook_boot() triggers an exception and doesn't result in the theme registry being populated. Perhaps not as important to test, but wouldn't hurt I suppose.

Dries’s picture

I like the bugfix but I'm not sure about the extra tests. It is not clear we actually need them -- this might well be tested already through other code. I'm going to commit the bugfix, and then maybe we can create a new issue for the tests.

Dries’s picture

Priority: Critical » Normal
Status: Reviewed & tested by the community » Needs work

Thanks all. I committed the fix but not the tests. Let's move the test to a new issue -- they are not testing the bug to begin with.

David_Rothstein’s picture

Status: Needs work » Fixed

Dries, no, the point is they are testing the bug that one of the earlier patches fixed. See #47. And I doubt we have test coverage for this, because if we did, the patches that kept breaking it would not have been committed :) This has broken more times than I can count.

#764094: Add tests to check that the theme system works in hook_init (i.e., as early as possible)

Since I moved the patch there, pretty sure there's nothing more to do here, so marking it fixed.

Status: Fixed » Closed (fixed)

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

Damien Tournoud’s picture

Version: 7.x-dev » 6.x-dev
Status: Closed (fixed) » Patch (to be ported)

I suggest we backport this. The fact that an incomplete theme registry can be saved just caused me some grief.

catch’s picture

Priority: Normal » Major
Status: Patch (to be ported) » Needs review
FileSize
1.43 KB

I just debugged this on a D6 site. When this happens it renders the entire site completely blank.

Here's an untested backport of the original patch for D6 (not 100% direct backport but close). Adds an optional argument to module_load_all() which is different to the optional argument in D7, can't do a lot about that I think.

catch’s picture

FileSize
1.64 KB

Slightly better version - prevents static caching of the invalid registry too.

Status: Needs review » Closed (outdated)

Automatically closed because Drupal 6 is no longer supported. If the issue verifiably applies to later versions, please reopen with details and update the version.