drupal_get_js() and drupal_aggregate_css() call is_writable() in each request to see whether the public:// directory is writable. When JS and CSS aggregation is enabled, they also call file_prepare_directory() in each request.

This is a bit too much. The file permissions for this directory is checked not only on admin/config/media/file-system (“The directory sites/default/files exists but is not writable and could not be made writable”) but also on admin/config/development/performance (“Set up the public files directory to make these optimizations available”).

This patch removes this check. This saves a two stat calls on each request. I know this is micro-optimization, but since is also reduces code complexity slightly, I think it is worth considering.

If the directory becomes unwritable, CSS aggregation fails and the CSS files are just included separately, i.e. the site will still look the same.

For JS aggregation, the JS files are omitted. In order to include them like the CSS case would require a few extra lines of code, but I don't think it's worth doing this just to allow JS to be included during an extraordinary error situation.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Damien Tournoud’s picture

Category: task » bug
Priority: Minor » Critical

Depending on the underlying file storage, this is_writable can be awfully expensive.

Requalifying as a critical bug.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

justification and code look good to me.

c960657’s picture

FileSize
9.08 KB

Added “, or FALSE if the file could not be saved” to the @return description of drupal_build_css_cache() and drupal_build_js_cache().

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD. Thanks.

c960657’s picture

Version: 7.x-dev » 6.x-dev
Status: Fixed » Needs review
FileSize
10.7 KB

Backport for D6.

mikeytown2’s picture

subscribe; will be testing for 6.x sooner rather then later. We have the css dir directly mapped to S3 so when S3 went down today, bad things happened.
http://aws.amazon.com/s3/#protecting
http://twitter.com/search?q=amazon%20s3
Fun.

Related contrib issue http://drupal.org/node/652690#comment-3039006 since #228818: IE: Stylesheets ignored after 31 link/style tags is fixed in 7.x

mikeytown2’s picture

FileSize
7.4 KB

Above patch fails to output css if you manually set drupal_build_css_cache() to return FALSE. This patch makes the css work in case this edge case happens.

      if ($preprocess_file) {
        $output .= '<link type="text/css" rel="stylesheet" media="'. $media .'" href="'. base_path() . $preprocess_file .'" />'."\n";
      }
      else {
        // Redo with preprocess_css turned off and return the new value.
        $org_preprocess_css = $conf['preprocess_css'];
        $conf['preprocess_css'] = FALSE;
        $data = drupal_get_css();
        $conf['preprocess_css'] = $org_preprocess_css;
        return $data;
      }
mikeytown2’s picture

FileSize
8.96 KB

Added a check for css/js filesize of zero; throw a watchdog error when the fallback codepath is run.

gooddesignusa’s picture

subscribing

c960657’s picture

Is the watchdog() necessary? AFAICT file_save_data() already writes an entry in case it cannot save the file.

Why is the filesize() check necessary in the D6 patch? If it is generally useful, it should be committed to HEAD first and then backported.

mikeytown2’s picture

If the filesize ends up zero (which it can happen if using something like FUSE) then file_save_data will not throw an error since the file was saved with a filesize of zero. It happens (filesize zero) about once a day for me, so I find it very useful. There are some things that need to go upstream from my 6.x patch; the biggest one is the fallback code path #7. The filesize zero might be happen due to a race condition
#818818: Race Condition when using file_save_data FILE_EXISTS_REPLACE, still investigating.

c960657’s picture

Status: Needs review » Needs work

New code should be introduced in 7.x and then backported to 6.x, not the other way around. Let's deal with the filesize() issue in a separate issue.

mikeytown2’s picture

Version: 6.x-dev » 7.x-dev
Status: Needs work » Needs review
FileSize
5.03 KB

I'm not sure of the 7.x function I need to modify, but I think its drupal_aggregate_css(). Patch for 7.x including the changes I did to the 6.x patch.

carlos8f’s picture

subscribing

c960657’s picture

Could you please open a separate issue regarding the filesize() issue? I don't think it is related to the performance improvement that was the original subject of this issue.

mikeytown2’s picture

FileSize
3.43 KB

Split patch: #828268: Prevent serving JS/CSS files if they have a filesize of zero
Reason filesize ended up in here was this is where the return FALSE code patch came from, which the filesize patch uses.

mikeytown2’s picture

What do you think about adding in a lock to prevent multiple attempts at creating the same file? It would go in a separate issue.

chx’s picture

Title: Don't call is_writable() in each request » CSS aggregator fails when the directory is not writeable
Status: Needs review » Reviewed & tested by the community
FileSize
3.18 KB

I think the code is sound, I only rerolled to fix a typo in comments (sense should be since) and I retitled the issue because the is_writable fix has been committed since and so it was kinda confusing as the patch did not touch is_writable.

Dries’s picture

Priority: Critical » Normal

I'm setting the priority to 'normal' -- this seems like an edge case to me.

+++ includes/common.inc	2010-06-18 22:26:15 +0000
@@ -2893,16 +2893,28 @@ function drupal_group_css($css) {
+  $redo = FALSE;

$redo is an odd variable name. I don't understand why it is called $redo.

I don't think the code, with the handling of $conf, is particularly elegant.

mikeytown2’s picture

Sorry about the critical; never changed the status once I found a hole in the logic and ported the 6.x code back up to 7.x.

I agree $conf is not that elegant; the other option would be to pass a variable so preprocess_css & preprocess_js can be disabled for the second call of the function. The more complex option is to redo the logic inside drupal_aggregate_css & drupal_get_js so it makes a copy of $processed or $css_groups. So if the writing of the aggregated file fails it can revert back to the unmodified version and not serve the page with a missing css/js file.

Should we stick with $conf or should I rewrite these functions. Rewrite so they are non destructive on the variable that holds the uri & thus this function doesn't need to be run twice; the logic will be slightly more complex most likely. What path should we take?

mikeytown2’s picture

Title: CSS aggregator fails when the directory is not writeable » CSS/JS aggregator fails when the directory is not writeable
Status: Reviewed & tested by the community » Needs review
FileSize
2.65 KB

Reworked this. The way css is aggregated in comparison to the way js is aggregated is different. Taking these differences into account I was able to reduce the ugly in comparison to the previous patches. CSS is elegant while JS is a little ugly still. Fixing the ugly JS processing would probably be in a different issue.
#330082: Refactor drupal_get_js() to use render layer similar to drupal_get_css()
#595572: Clean up CSS and JS processing

Tested this code by having drupal_build_css_cache return FALSE and by having drupal_build_js_cache return FALSE. Without the patch, css and/or js is missing. With the patch they show up in a non aggregated form when aggregation fails.

EDIT: need to fix some comments in the patch; other then this, how does it look?

Status: Needs review » Needs work
Issue tags: -Performance

The last submitted patch, drupal-755586-20.patch, failed testing.

mikeytown2’s picture

Status: Needs work » Needs review
Issue tags: +Performance

#21: drupal-755586-20.patch queued for re-testing.

mikeytown2’s picture

Priority: Normal » Major
mikeytown2’s picture

sun.core’s picture

Status: Needs review » Needs work
Issue tags: -Performance
+++ includes/common.inc	29 Jun 2010 23:21:36 -0000
@@ -3831,6 +3836,18 @@ function drupal_get_js($scope = 'header'
+      // If aggregation failed then add include each js file
+      else {
+        unset($processed[$key]);
+        foreach ($file_set as $item) {
+          if ($item['defer']) {
+            $js_element['#attributes']['defer'] = 'defer';
+          }
+          $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?';
+          $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME);
+          $processed[$index++] = theme('html_tag', array('element' => $js_element));
+        }
+      }

It looks like this is duplicating lot of (much more complex) code. We need to intercept this special condition earlier in the process.

Powered by Dreditor.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
3.32 KB

Here's a different way of doing the JS part.

sun’s picture

Version: 7.x-dev » 8.x-dev
Issue tags: +Needs tests, +Needs backport to D7

It looks like we could test this by attempting to write to a non-writeable directory?

catch’s picture

Title: CSS/JS aggregator fails when the directory is not writeable » Fallback for CSS/JS aggregation for non-writable directories
Priority: Major » Normal

Patch looks fine in principle but I agree it would be good to have tests here.

I also disagree this is even major, non-writeable directories are misconfiguration, and this does not have severe side-effects (i.e. it just doesn't work, then it starts working when the directories become writeable again).

mikeytown2’s picture

Full drive, NFS issues, S3 issues. Reason why it was set to major. We are off of S3 so in my case it is no longer a major issue.

pbz1912’s picture

Status: Needs review » Needs work

patch no longer applies.

YesCT’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests, -Needs backport to D7

#27: drupal-755586-27.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +Needs tests, +Needs backport to D7

The last submitted patch, drupal-755586-27.patch, failed testing.

star-szr’s picture

Issue tags: +Novice, +Needs reroll

Tagging.

YesCT’s picture

tags did not stick. trying again.

ooystein’s picture

Status: Needs work » Needs review
FileSize
1.25 KB

Rewritten patch for D8. Could need tests!

YesCT’s picture

Issue tags: -Needs reroll

looks like that patch applies to 8.x, so removing needs reroll tag.

ooystein’s picture

#755586-28: Fallback for CSS/JS aggregation for non-writable directories : It looks like we could test this by attempting to write to a non-writeable directory?

Yes, but I don't see how you can do that in a test. If you can change the directory permissions in the test then the call to file_prepare_directory() in drupal_build_js_cache and drupal_build_css_cache() will be able to change the permissions back to writable. You would have to change the owner of the directory to really test this out.

Anyone got an idea on how to write a test for this?

socketwench’s picture

Issue summary: View changes
Issue tags: -Novice

Novice issue cleanup.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Unsure if this is still an issue, but the patch above definitely no longer applies.

Nitesh Sethia’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
153.44 KB

Rerolled the patch

Status: Needs review » Needs work

The last submitted patch, 41: fallback_for_css_js-755586-41.patch, failed testing.

Nitesh Sethia’s picture

Assigned: Unassigned » Nitesh Sethia
mikeytown2’s picture

Just a heads up that AdvAgg has this built in if someone is looking for a 7.x solution.

mgifford’s picture

Version: 8.0.x-dev » 8.1.x-dev
Assigned: Nitesh Sethia » Unassigned

Unassigning for now. Bumping to 8.1 too.

catch’s picture

Version: 8.1.x-dev » 7.x-dev

Note that #1014086: Stampedes and cold cache performance issues with css/js aggregation removes all i/o calls from the main request.

Also #2506369: Cache CSS/JS asset resolving means that i/o is not every single request in 8.x.

Given those two issues, I'd be tempted to move this issue back to 7.x. Both agrcache and advagg cover this from contrib.

mgifford’s picture

Even better, thanks @catch!

  • Dries committed d35cf3f on 8.3.x
    - Patch #755586 by c960657: don't call is_writable() in each request.
    
    

  • Dries committed d35cf3f on 8.3.x
    - Patch #755586 by c960657: don't call is_writable() in each request.
    
    

  • Dries committed d35cf3f on 8.4.x
    - Patch #755586 by c960657: don't call is_writable() in each request.
    
    

  • Dries committed d35cf3f on 8.4.x
    - Patch #755586 by c960657: don't call is_writable() in each request.
    
    

  • Dries committed d35cf3f on 9.1.x
    - Patch #755586 by c960657: don't call is_writable() in each request.