Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1266
diff -u -p -r1.1266 common.inc
--- includes/common.inc	30 Nov 2010 06:30:21 -0000	1.1266
+++ includes/common.inc	30 Nov 2010 09:19:11 -0000
@@ -3080,7 +3080,12 @@ function drupal_aggregate_css(&$css_grou
       // the group's data property to the file path of the aggregate file.
       case 'file':
         if ($group['preprocess'] && $preprocess_css) {
-          $css_groups[$key]['data'] = drupal_build_css_cache($group['items']);
+          $uri = drupal_build_css_cache($group['items']);
+          // Only include the file if was written successfully. Errors are
+          // logged using watchdog.
+          if ($uri) {
+            $css_groups[$key]['data'] = $uri;
+          }
         }
         break;
       // Aggregate all inline CSS content into the group's data property.
@@ -3343,68 +3348,122 @@ function drupal_pre_render_styles($eleme
  *   The URI of the CSS cache file, or FALSE if the file could not be saved.
  */
 function drupal_build_css_cache($css) {
-  $data = '';
   $uri = '';
   $map = variable_get('drupal_css_cache_files', array());
   $key = hash('sha256', serialize($css));
   if (isset($map[$key])) {
     $uri = $map[$key];
+    if (file_exists($uri)) {
+      return $uri;
+    }
   }
 
-  if (empty($uri) || !file_exists($uri)) {
-    // Build aggregate CSS file.
-    foreach ($css as $stylesheet) {
-      // Only 'file' stylesheets can be aggregated.
-      if ($stylesheet['type'] == 'file') {
-        $contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
-
-        // Build the base URL of this CSS file: start with the full URL.
-        $css_base_url = file_create_url($stylesheet['data']);
-        // Move to the parent.
-        $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
-        // Simplify to a relative URL if the stylesheet URL starts with the
-        // base URL of the website.
-        if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
-          $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
-        }
-
-        _drupal_build_css_path(NULL, $css_base_url . '/');
-        // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
-        $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
+  $lock_name = 'css_cache:' . $key;
+  $lock_attempts = 0;
+  while ($lock_attempts++ < 5) {
+    $lock_acquired = lock_acquire($lock_name);
+
+    // Reload the CSS map directly from the database.
+    $variables = variable_initialize();
+    $map = isset($variables['drupal_css_cache_files']) ? $variables['drupal_css_cache_files'] : array();
+
+    if ($lock_acquired) {
+      // We got the lock, but the CSS file has changed since we started trying
+      // to rebuild it, so another process did the work. Release the lock and
+      // return the new URI.
+      if (isset($map[$key]) && $map[$key] != $uri) {
+        lock_release($lock_name);
+        return $map[$key];
       }
+      break;
     }
+    else {
+      // We didn't get the lock, so check if there's a file we can serve.
+      if (isset($map[$key]) && file_exists($map[$key])) {
+        return $map[$key];
+      }
 
-    // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
-    // @import rules must proceed any other style, so we move those to the top.
-    $regexp = '/@import[^;]+;/i';
-    preg_match_all($regexp, $data, $matches);
-    $data = preg_replace($regexp, '', $data);
-    $data = implode('', $matches[0]) . $data;
-
-    // Prefix filename to prevent blocking by firewalls which reject files
-    // starting with "ad*".
-    $filename = 'css_' . drupal_hash_base64($data) . '.css';
-    // Create the css/ within the files folder.
-    $csspath = 'public://css';
-    $uri = $csspath . '/' . $filename;
-    // Create the CSS file.
-    file_prepare_directory($csspath, FILE_CREATE_DIRECTORY);
-    if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
-      return FALSE;
-    }
-    // If CSS gzip compression is enabled, clean URLs are enabled (which means
-    // that rewrite rules are working) and the zlib extension is available then
-    // create a gzipped version of this file. This file is served conditionally
-    // to browsers that accept gzip using .htaccess rules.
-    if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
-      if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
-        return FALSE;
+      // No file and no lock, so try again.
+      lock_wait($lock_name);
+    }
+  }
+
+  // We got the lock, or there's no aggregated CSS file for this $key.
+  $data = '';
+  foreach ($css as $stylesheet) {
+    // Only 'file' stylesheets can be aggregated.
+    if ($stylesheet['type'] == 'file') {
+      $contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
+
+      // Build the base URL of this CSS file: start with the full URL.
+      $css_base_url = file_create_url($stylesheet['data']);
+      // Move to the parent.
+      $css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
+      // Simplify to a relative URL if the stylesheet URL starts with the
+      // base URL of the website.
+      if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
+        $css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
       }
+
+      _drupal_build_css_path(NULL, $css_base_url . '/');
+      // Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
+      $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
+    }
+  }
+
+  // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import,
+  // @import rules must proceed any other style, so we move those to the top.
+  $regexp = '/@import[^;]+;/i';
+  preg_match_all($regexp, $data, $matches);
+  $data = preg_replace($regexp, '', $data);
+  $data = implode('', $matches[0]) . $data;
+
+  // Create the css/ directory within the files folder.
+  $csspath = 'public://css';
+  file_prepare_directory($csspath, FILE_CREATE_DIRECTORY);
+
+  // Prefix filename to prevent blocking by firewalls which reject files
+  // starting with "ad*", and create the CSS file.
+  $filename = 'css_' . drupal_hash_base64($data) . '.css';
+  $old_uri = $uri;
+  $uri = $csspath . '/' . $filename;
+  if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
+    // The write failed, so release the lock and return an old URI
+    // if there is one.
+    lock_release($lock_name);
+    return $old_uri && file_exists($old_uri) ? $old_uri : FALSE;
+  }
+
+  // Save the updated map atomically.
+  $lock_attempts = 0;
+  while (!lock_acquire('css_cache') && $lock_attempts++ < 5) {
+    lock_wait('css_cache');
+  }
+
+  // Be sure we have the latest version of the css map.
+  $variables = variable_initialize();
+  $map = isset($variables['drupal_css_cache_files']) ? $variables['drupal_css_cache_files'] : array();
+  $map[$key] = $uri;
+  variable_set('drupal_css_cache_files', $map);
+  lock_release('css_cache');
+
+  // Release the lock now, because the file is written to disk and map
+  // updated, so we've prevented a potential request stampede.
+  lock_release($lock_name);
+
+  // If CSS gzip compression is enabled, clean URLs are enabled (which
+  // means that rewrite rules are working) and the zlib extension is
+  // available then create a gzipped version of this file. This file is
+  // served conditionally to browsers that accept gzip using .htaccess
+  // rules.
+  if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
+    if (!file_exists($uri . '.gz')) {
+      // Ignore an error saving the file. Requests will fall back
+      // to the non-gzip copy of the file we have already written.
+      file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE);
     }
-    // Save the updated map.
-    $map[$key] = $uri;
-    variable_set('drupal_css_cache_files', $map);
   }
+
   return $uri;
 }
 
@@ -3576,7 +3635,12 @@ function _drupal_load_stylesheet($matche
  * Deletes old cached CSS files.
  */
 function drupal_clear_css_cache() {
+  $lock_attempts = 0;
+  while (!lock_acquire('css_cache') && $lock_attempts++ < 5) {
+    lock_wait('css_cache');
+  }
   variable_del('drupal_css_cache_files');
+  lock_release('css_cache');
   file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
 }
 
@@ -4698,45 +4762,101 @@ function drupal_add_tabledrag($table_id,
  *   The URI of the cache file, or FALSE if the file could not be saved.
  */
 function drupal_build_js_cache($files) {
-  $contents = '';
   $uri = '';
   $map = variable_get('drupal_js_cache_files', array());
   $key = hash('sha256', serialize($files));
   if (isset($map[$key])) {
     $uri = $map[$key];
+    if (file_exists($uri)) {
+      return $uri;
+    }
   }
 
-  if (empty($uri) || !file_exists($uri)) {
-    // Build aggregate JS file.
-    foreach ($files as $path => $info) {
-      if ($info['preprocess']) {
-        // Append a ';' and a newline after each JS file to prevent them from running together.
-        $contents .= file_get_contents($path) . ";\n";
+  $lock_name = 'js_cache:' . $key;
+  $lock_attempts = 0;
+  while ($lock_attempts++ < 5) {
+    $lock_acquired = lock_acquire($lock_name);
+
+    // Reload the javacript map directly from the database.
+    $variables = variable_initialize();
+    $map = isset($variables['drupal_js_cache_files']) ? $variables['drupal_js_cache_files'] : array();
+
+    if ($lock_acquired) {
+      // We got the lock, but the javacript file has changed since we started
+      // trying to rebuild it, so another process did the work. Release the lock and
+      // return the new URI.
+      if (isset($map[$key]) && $map[$key] != $uri) {
+        lock_release($lock_name);
+        return $map[$key];
       }
+      break;
     }
-    // Prefix filename to prevent blocking by firewalls which reject files
-    // starting with "ad*".
-    $filename = 'js_' . drupal_hash_base64($contents) . '.js';
-    // Create the js/ within the files folder.
-    $jspath = 'public://js';
-    $uri = $jspath . '/' . $filename;
-    // Create the JS file.
-    file_prepare_directory($jspath, FILE_CREATE_DIRECTORY);
-    if (!file_exists($uri) && !file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) {
-      return FALSE;
-    }
-    // If JS gzip compression is enabled, clean URLs are enabled (which means
-    // that rewrite rules are working) and the zlib extension is available then
-    // create a gzipped version of this file. This file is served conditionally
-    // to browsers that accept gzip using .htaccess rules.
-    if (variable_get('js_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
-      if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) {
-        return FALSE;
+    else {
+      // We didn't get the lock, so check if there's a file we can serve.
+      if (isset($map[$key]) && file_exists($map[$key])) {
+        return $map[$key];
       }
+
+      // No file and no lock, so try again.
+      lock_wait($lock_name);
+    }
+  }
+
+  // We got the lock, or there's no aggregated javascript file for this $key.
+  $data = '';
+  foreach ($files as $path => $info) {
+    if ($info['preprocess']) {
+      // Append a ';' and a newline after each JS file to prevent them from running together.
+      $data .= file_get_contents($path) . ";\n";
     }
-    $map[$key] = $uri;
-    variable_set('drupal_js_cache_files', $map);
   }
+
+  // Create the js/ within the files folder.
+  $jspath = 'public://js';
+  file_prepare_directory($jspath, FILE_CREATE_DIRECTORY);
+
+  // Prefix filename to prevent blocking by firewalls which reject files
+  // starting with "ad*", and create the javascript file.
+  $filename = 'js_' . drupal_hash_base64($data) . '.js';
+  $old_uri = $uri;
+  $uri = $jspath . '/' . $filename;
+  if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) {
+    // The write failed, so release the lock and return an old URI
+    // if there is one.
+    lock_release($lock_name);
+    return $old_uri && file_exists($old_uri) ? $old_uri : FALSE;
+  }
+
+  // Atomically save the updated map.
+  $lock_attempts = 0;
+  while (!lock_acquire('js_cache') && $lock_attempts++ < 5) {
+    lock_wait('js_cache');
+  }
+
+  // Be sure we have the latest version of the javascript map.
+  $variables = variable_initialize();
+  $map = isset($variables['drupal_js_cache_files']) ? $variables['drupal_js_cache_files'] : array();
+  $map[$key] = $uri;
+  variable_set('drupal_js_cache_files', $map);
+  lock_release('js_cache');
+
+  // Release the lock now, because the file is written to disk and map
+  // updated, so we've prevented a potential request stampede.
+  lock_release($lock_name);
+
+  // If JS gzip compression is enabled, clean URLs are enabled (which
+  // means that rewrite rules are working) and the zlib extension is
+  // available then create a gzipped version of this file. This file is
+  // served conditionally to browsers that accept gzip using .htaccess
+  // rules.
+  if (variable_get('js_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) {
+    if (!file_exists($uri . '.gz')) {
+      // Ignore an error saving the file. Requests will fall back
+      // to the non-gzip copy of the file we have already written.
+      file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE);
+    }
+  }
+
   return $uri;
 }
 
@@ -4744,8 +4864,13 @@ function drupal_build_js_cache($files) {
  * Deletes old cached JavaScript files and variables.
  */
 function drupal_clear_js_cache() {
+  $lock_attempts = 0;
+  while (!lock_acquire('js_cache') && $lock_attempts++ < 5) {
+    lock_wait('js_cache');
+  }
   variable_del('javascript_parsed');
   variable_del('drupal_js_cache_files');
+  lock_release('js_cache');
   file_scan_directory('public://js', '/.*/', array('callback' => 'drupal_delete_file_if_stale'));
 }
 
