This was discovered during #1176856: Anonymous users get failure if page_cache_without_database = TRUE and page_cache_invoke_hooks = FALSE.
What I did
I set page_cache_without_database to TRUE via settings.php to make full use of the APC cache backend for pages.
Result
White pages & browser errors complaining about invalid compression data when visiting the site as an anonymous user.
Cause
Variables are not loaded when serving a cache hit when page_cache_without_database = TRUE. Variables are loaded when the caching system needs to serve a cache miss. This causes an 'impedance mismatch' between the code that fetches and serves a page from cache and code that saves a page to cache when the variable page_compression is set to FALSE in the interface;
Upon serving a cache miss, page_compression = FALSE and an uncompressed page is saved to cache
Upon serving a cache hit, page_compression has its default value TRUE and drupal_serve_page_from_cache serves the uncompressed data with a Content-Encoding: gzip header. Browsers do not appreciate this.
Workarounds
- Check the setting Compress cached pages on admin/config/development/performance
- Set page_compresssion to FALSE in settings.php's conf array.
Possible solution
drupal_page_set_cache should use the default value of page_compression if page_cache_without_database is set to TRUE.
Comment | File | Size | Author |
---|---|---|---|
#54 | 1476810-54.patch | 1.95 KB | andypost |
Comments
Comment #1
franzD8 has a different way of loading settings, so I'm not sure if the bug exists too.
Regarding D7, this is a scratch of a patch. I don't know if this approach is right, as it will ignore page_compression if page_cache_without_database is set.
Are there any tests for this kind of funcionality? Shouldn't be hard, I guess.
Comment #2
franzForgot the patch
Comment #3
franzThinking of a better solution here, why don't we set $cache->data['page_compression'] and when delivering the page check that instead of variable page_compression?
Alternatively, we don't change any code, and just warn the user on performance page about the usage of non_database, in which case page_compression must be set in settings.php.
Comment #4
Heine CreditAttribution: Heine commentedA separate cache->data entry is IMO the best solution.
Comment #5
drupalerocant CreditAttribution: drupalerocant commentedIs this fixed in drupal core at the moment?
I just had the same problem with APC installed.
I have drupal 7.16
I used the 2a configuration in the README file of the APC module and when adding this two lines to the settings.php:
after a while it gave me the following fatal error:
Fatal error: Call to undefined function db_query() includes/cache.inc on line 357
I deleted these two lines from the settings.php and everything went back to normal.
Comment #6
SpleshkaThe patch in #2 is works for me.
But it works only when "Compress cached pages" setting enabled.
I use Memcache to store cached data(settings.php):
Comment #7
tenken CreditAttribution: tenken commentedThe patch in #2 works for me as well.
I set APC to serve cached pages from memory without a DB hit. Prior to the patch I was getting the invalid compression error message from the browser.
edit: I'm using D 7.21
Comment #8
SpleshkaI digged deeper into code and found a real source of this problem.
Look at drupal_serve_page_from_cache() function. Pay your attention at this code:
It looks pretty good: if page_compression is disabled in UI the page won't be gziped and then unziped. BUT if we got $conf['page_cache_without_database'] = TRUE; in our settings.php the variables is NOT initialized and variable_get('page_compression', TRUE) will ALWAYS return TRUE!
So, even if page_compression is disabled, Drupal will try to unzip it, and we will get encoding issues:
The easiest way to avoid this is to set this variable in settings.php:
But we definately should log this case in the documentation. I'm not sure where is the right place to do that.
Comment #9
Heine CreditAttribution: Heine commentedSpleshka, you restated the info in starter. The proposed solution in #3 is to include compression information in the cache item itself, eliminating the mismatch.
Considering the speed of the issue and the ease of workaround, I've added the page Blank pages or browser errors after enabling page_cache_without_database to the Troubleshooting FAQ.
Issue should remain on "needs work" for reason stated in #3.
Comment #10
SpleshkaThanks @Heine. I'll start working on solution proposed in #3.
Comment #11
SpleshkaAs I promised, patch attached. Please, review it.
Comment #12
andypostNice idea, still needs test for rtbc
Comment #13
Spleshka@andypost,
Manual or Unit tests?
Comment #14
franzAutomated. Submit tests to demonstrate a test fail (thus, the bug), and a combined patch with test+code to demonstrate the fix.
Comment #15
SpleshkaI'm not sure how can I make an automated test for thia patch. It requires some external cache system (memcached, apc, or smth like that). Does Drupal test bot has required tools for that?
Comment #16
Damien Tournoud CreditAttribution: Damien Tournoud commentedBumping to Drupal 8, as it has to be fixed there first. Saving metadata with the page itself is a good idea.
Comment #17
SpleshkaI can provice patch for D8, but the question is still the same - how to use external cache in tests?
Comment #18
Damien Tournoud CreditAttribution: Damien Tournoud commentedJust unit testing
drupal_serve_page_from_cache()
would be enough. Don't spend too much time on it, the days of the page cache in its current form are numbered.Comment #19
SpleshkaOk, will try to provide a patch asap.
Comment #20
SpleshkaTest for D8 without patch (should fail).
Comment #21
SpleshkaPatch + test.
Comment #23
SpleshkaHm possibly problem in line endings. Fix and attach test again.
Comment #24
SpleshkaComment #25
SpleshkaAnd fixed patch with test.
Comment #27
SpleshkaOh, my bad again. Attached a new test with patch. The first one should fail while the second should pass.
Comment #28
chx CreditAttribution: chx commentedI think adding a line of comment to the new 'page_compressed' => $page_compressed part would be good -- like "Every page cache item needs to store whether it's compressed or not because by the time it is read, the configuration might change". Then it's perfect, thanks for the test, the separate upload, the patch, it's really nice work.
Comment #29
Spleshka@chx, agree. Attach updated patch.
Comment #30
chx CreditAttribution: chx commentedThanks again.
Comment #31
catchLooks good.
Committed/pushed to 8.x, moving to 7.x for backport.
Comment #32
SpleshkaAttach test and patch with test for D7.
With this patch we will solve two problems at one time:
$conf['page_cache_without_database'] = TRUE;
Drupal tried to decompress cached data, no matter whether compression mode was enabled or not.Comment #33
Damien Tournoud CreditAttribution: Damien Tournoud commentedA few comments about the patch that went in for Drupal 8:
(1)
This runtime check doesn't make sense. The fact that the zlib extension is loaded or not doesn't change the fact that the page was stored compressed or not. We need to drop the second part.
(2) The test is testing a side effect that I'm not even convinced is desirable (old pages are still served when the compression mode is changed). Could we look into unit testing the page cache instead?
(3) We need an update function that clears the old cached pages.
Comment #35
Spleshka@Damien Tournoud,
(1) good catch, we can drop this part.
(2) It doesn't seems to be nescessary, this test shows how cached pages works for anonymous after changing compression mode.
(3) What if page compression was changed using $conf in settings.php? In this case cached won't flush.
Comment #36
Damien Tournoud CreditAttribution: Damien Tournoud commentedThe problem is what's tested is just a side-effect of this patch. Even more, what's tested is arguably not a general property of "the page cache". We are essentially testing an implementation detail here, not a real feature.
I could totally see a page cache implementation that flushes all the entries when you change the compression mode. That would be totally legitimate (after all, the compression mode is a configuration option...), and will not affect the functionality that we name "page cache" in any way.
We just need an update function that unconditionally flushes the page cache, because the way we store pages changed, in a non-backward compatible way.
Comment #37
SpleshkaStill don't undestand why storing compression mode in cache is non-backward compatiple. I think that this feature helps to avoid problems with changing of the compression settings. Why should we flush all cached pages and cause a performance issues, if we can just use flag from cache?
Comment #38
Damien Tournoud CreditAttribution: Damien Tournoud commented@Spleshka: if you had compression enabled and update Drupal, we will serve compressed pages from the cache without the proper compression headers, because
$cache->data['page_compressed']
is empty. And yes, we should just flush the page cache, it is cheap to rebuilt.Comment #39
SpleshkaOh, now I got your point. But we can just flush cache pages after update to next release. And no, flushing cached pages on high-load sites may cause some temporary problems, so why should we do this?
Comment #40
Damien Tournoud CreditAttribution: Damien Tournoud commentedWe just need an update function that flushes the page cache. We do that on very high traffic sites all the time. Don't be afraid of it.
Comment #41
andypostThe empty
hook_update_N()
implementation should be added only to 7.x branch. 8.x is not needed for it's current stateComment #42
Spleshka@Damien Tournoud,
You mean just to flush cached pages once after update to the next release? If so, it is OK.
@andypost,
Yep. D8 just needs a little patch that removes code with
extension_loaded('zlib');
New patch for D7 attached.
Comment #43
SpleshkaAfter discussion with @Damien in irc, we concluded that comment for hook_update_N() should be better and more informative.
Comment #44
andypostGreat!
PS Let's get this in and then fix #33 in D8
Comment #45
SocialNicheGuru CreditAttribution: SocialNicheGuru commentedI had a slight issue with applying the patch.
Patch in #43 doesn't apply cleanly on D7. I am running 7.23.
patch -p1 < drupal-drupal_serve_page_from_cache-can-serve-uncompressed-1476810-43.patch
patching file includes/bootstrap.inc
Hunk #1 succeeded at 1288 (offset 10 lines).
patching file includes/common.inc
Hunk #1 succeeded at 5167 (offset 8 lines).
Hunk #2 succeeded at 5178 (offset 8 lines).
Hunk #3 succeeded at 5198 (offset 8 lines).
patching file modules/simpletest/tests/bootstrap.test
Hunk #1 FAILED at 219.
1 out of 1 hunk FAILED -- saving rejects to file modules/simpletest/tests/bootstrap.test.rej
patching file modules/system/system.install
Hunk #1 succeeded at 3116 (offset 14 lines).
more modules/simpletest/tests/bootstrap.test.rej
--- modules/simpletest/tests/bootstrap.test
+++ modules/simpletest/tests/bootstrap.test
@@ -219,6 +219,18 @@
$this->assertFalse($this->drupalGetHeader('Content-Encoding'), 'A Content-Encoding header was not sent.');
$this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), 'Site title matches.');
$this->assertRaw('', 'Page was not compressed.');
+
+ // Disable compression mode.
+ variable_set('page_compression', FALSE);
+
+ // Verify if cached page is still available for a client with compression support.
+ $this->drupalGet('', array(), array('Accept-Encoding: gzip,deflate'));
+ $this->drupalSetContent(gzinflate(substr($this->drupalGetContent(), 10, -8)));
+ $this->assertRaw('', 'Page was delivered after compression mode is changed (compression support enabled).');
+
+ // Verify if cached page is still available for a client without compression support.
+ $this->drupalGet('');
+ $this->assertRaw('', 'Page was delivered after compression mode is changed (compression support disabled).');
}
}
Comment #46
Spleshka@SocialNicheGuru,
Patch should be applied to 7.x-1.x, not for a stable release.
Comment #47
SpleshkaAny progress with getting this in core?
Comment #48
SpleshkaErm... I think that this issue is quite important for high-load web sites, and it should be commited.
Comment #49
SpleshkaGuys, please tell me if here remained some work to do before we can commit this patch. But it seems already tested for me.
Comment #50
andypostLet's get David opinion
Comment #51
David_Rothstein CreditAttribution: David_Rothstein commentedThe patch no longer works since a different version of system_update_7079() was recently added to the codebase. However, since it will go out in the same release, it will do the same job that the empty update function was trying to do (namely, force a cache clear).
So here is a reroll with the update function removed. Will commit before the upcoming release, as long as it still passes tests.
And then we have to bump this back to Drupal 8 to fix issues with that patch (#33.1)?... Normally would have done that first (or else in a separate issue), but it looks like the fix is pretty small.
Comment #52
andypostPatch is green and yes, once next core version already adds update function there's no reason in new one.
After 7.x commit it makes sense to fix 8.x with a small follow-up patch to address #33.1
Comment #53
David_Rothstein CreditAttribution: David_Rothstein commentedCommitted to 7.x - thanks! http://drupalcode.org/project/drupal.git/commit/6cd1aae
Back to Drupal 8 for that small followup mentioned above.
Comment #54
andypostWhat core should send to client when data compressed but no way to decompress?
I think we should throw exception and because fallback to serving page without cache will just hide the problem.
Patch for 33.1 follow-up
Comment #55
jhedstromComment #56
oakulm CreditAttribution: oakulm commentedComment #57
oakulm CreditAttribution: oakulm as a volunteer commentedI'm closing this issue because it seems to be outdated. Feel free to reopen and update the issue summary if the problem still persists.