http://api.drupal.org/api/function/drupal_build_css_cache/6
http://api.drupal.org/api/function/drupal_build_js_cache/6
http://api.drupal.org/api/function/_color_save_stylesheet/6

All 3 of these function call file_save_data() with FILE_EXISTS_REPLACE. file_move() -> file_save_data() -> file_move() -> file_copy(). Once in file_copy it finally calls copy(). The copy function is slow in comparison to rename(); rename is also safer in this case, less likely for a race condition.

CommentFileSizeAuthor
#52 drupal-818818-52-file-save-D8.patch1.75 KBmikeytown2
#51 drupal-818818-51-file-save-D7.patch1.73 KBmikeytown2
#50 drupal-818818-50-file-save-D6.patch2.2 KBmikeytown2
#47 drupal-818818-47-file-save-D8.patch1.75 KBmikeytown2
#47 drupal-818818-47-file-save-D7-do-not-test.patch1.73 KBmikeytown2
#47 drupal-818818-47-file-save-D6-do-not-test.patch2.22 KBmikeytown2
#46 file-save-818818-46.patch1.75 KBoriol_e9g
#45 copy_remane_benchmarks .txt1.28 KBoriol_e9g
#41 drupal-818818-41-D7.patch1.92 KBmikeytown2
#37 drupal-818818-37-D7.patch1.76 KBmikeytown2
#36 drupal-818818-H-D7.patch1.64 KBmikeytown2
#34 drupal-818818-G-D7.patch1.64 KBmikeytown2
#32 drupal-818818-F-D7.patch1.65 KBmikeytown2
#31 drupal-818818-A-D7.patch1.62 KBmikeytown2
#31 drupal-818818-B-D7.patch1.62 KBmikeytown2
#31 drupal-818818-C-D7.patch1.62 KBmikeytown2
#31 drupal-818818-D-D7.patch1.61 KBmikeytown2
#31 drupal-818818-E-D7.patch1.61 KBmikeytown2
#30 drupal-818818-D6.patch1.63 KBmikeytown2
#30 drupal-818818-A-D7.patch1.6 KBmikeytown2
#30 drupal-818818-B-D7.patch1.61 KBmikeytown2
#30 drupal-818818-C-D7.patch1.62 KBmikeytown2
#30 drupal-818818-D-D7.patch1.6 KBmikeytown2
#30 drupal-818818-E-D7.patch1.59 KBmikeytown2
#24 drupal-818818-D6.patch1.07 KBmikeytown2
#23 drupal-818818-D7.patch1.17 KBmikeytown2
#21 drupal-818818-D7.patch1.19 KBmikeytown2
#20 drupal-818818-D7.patch1.17 KBmikeytown2
#19 drupal-818818-D7.patch1.17 KBmikeytown2
#18 drupal-818818-D7.patch1.4 KBmikeytown2
#15 drupal-818818-D7.patch1.39 KBmikeytown2
#11 drupal-818818-D7.patch1.2 KBmikeytown2
#9 drupal-818818-D7.patch1.18 KBmikeytown2
#6 drupal-818818-D7.patch1.12 KBmikeytown2
#4 drupal-818818-D7.patch1.08 KBmikeytown2
#3 drupal-818818-D7.patch985 bytesmikeytown2
#1 drupal-818818-D6.patch873 bytesmikeytown2
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mikeytown2’s picture

Status: Active » Needs review
FileSize
873 bytes

This is similar to what I use in the boost module.

c960657’s picture

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

file_copy() should not remove the source file. We need to introduce a temporary file, possibly in the same directory (because renames across partitions/mounts are not atomic).

This needs to be fixed in HEAD and then backported.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
985 bytes

7.x equivalent is file_unmanaged_copy() from what I can tell.

mikeytown2’s picture

FileSize
1.08 KB

Patch with rename in the same dir level now & early abort if the first copy fails.

Status: Needs review » Needs work

The last submitted patch, drupal-818818-D7.patch, failed testing.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.12 KB

Think I fixed the php error & incorrect calling of drupal_tempnam.

carlos8f’s picture

subscribing

Status: Needs review » Needs work

The last submitted patch, drupal-818818-D7.patch, failed testing.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.18 KB

trying file_put_contents after the drupal_tempnam call instead of copy.

mikeytown2’s picture

Status: Needs review » Needs work
mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.2 KB

Status: Needs review » Needs work

The last submitted patch, drupal-818818-D7.patch, failed testing.

mikeytown2’s picture

Looks like I need to fire up 7.x in order to figure this one out.

mikeytown2’s picture

When I run the test locally I get errors on file_unmanaged_delete_recursive & file_unmanaged_delete.

unlink(C:\xampplite\htdocs\d7\sites\default\files\simpletest\312646\private\temp\.htaccess): Permission denied - file_unmanaged_delete()
rmdir(C:\xampplite\htdocs\d7\sites\default\files\simpletest\312646\private\temp): Directory not empty - file_unmanaged_delete_recursive()

These are the only errors I get. Any ideas?
.htaccess is marked read only, is this the issue?

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.39 KB

This patch passes all tests on my local box.
Why did I have to add in chmod($path, 0777) to file_unmanaged_delete()?

Edit:Think I ran the wrong test (File), should have been (File API). Have a feeling this will bomb as well... I'll see how the local one goes for me...

Status: Needs review » Needs work

The last submitted patch, drupal-818818-D7.patch, failed testing.

c960657’s picture

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.4 KB

Going to try it without using drupal_tempnam. I have a feeling this function isn't working as I'm expecting it to.

mikeytown2’s picture

FileSize
1.17 KB

without chmod

mikeytown2’s picture

FileSize
1.17 KB

Trying $temp_name = drupal_tempnam(dirname($destination . '/'), 'file');

mikeytown2’s picture

FileSize
1.19 KB

Helps to grab the right file...

Status: Needs review » Needs work

The last submitted patch, drupal-818818-D7.patch, failed testing.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.17 KB

Going ahead & not using drupal_tempnam. Besides this point, how does the patch look?

mikeytown2’s picture

FileSize
1.07 KB

Re-roll of 6.x based off the latest changes to the 7.x patch

c960657’s picture

Status: Needs review » Needs work
+  if ($replace == FILE_EXISTS_REPLACE) {

Please add a comment explaining why we have two different code paths here.

The else part covering the two other modes could be extended with a fopen($file, 'x') call to ensure that existing files are never overwritten (another race condition). But that is probably a separate issue.

You may also add a comment mentioning that the unlink() call is necessary, because rename() cannot overwrite existing files on Windows. It would be nice if we could avoid the unlink() on platforms where rename() supports overwriting, though (one approach can be seen here: http://www.smarty.net/forums/viewtopic.php?t=6956&postdays=0&postorder=a...).

+    $temp_name = $destination . '-' . mt_rand();
+    if ($temp_name && @copy($source, $temp_name)) {

$temp_name will always evalute to TRUE here.

Could we choose a name that looks more "temporary"? E.g. add ".tmp" or something.
Elsewhere in core, e.g. in lock.inc we use uniqid(mt_rand(), TRUE) for generating a unique random identifier. But why not use drupal_tempnam() ? If there is a good reason, please add a comment explaining why.

mikeytown2’s picture

@c960657
I tried drupal_tempnam many times in different ways but it always fails the testbot. That's the reason there are so many fails in this thread.

c960657’s picture

Why does the testbot not like it?

Do the tests pass on your local machine? If they do, we should fix the testbot (or whatever is causing the tests to fail), rather than implementing workarounds.

mikeytown2’s picture

file_directory_temp uses OS detection so I wouldn't feel bad doing it again in core

Option A

      if ($temp_name && @copy($source, $temp_name)) {
        if (!$result = @rename($temp_name, $dest)) {
          @unlink($dest);
          $result = @rename($temp_name, $dest);
        }
      }

Option B

      if ($temp_name && @copy($source, $temp_name)) {
        if (substr(PHP_OS, 0, 3) == 'WIN' && file_exists($dest)) {
          @unlink($dest);
          $result = @rename($temp_name, $dest);
        }
        else {
          $result = @rename($temp_name, $dest);
        }
      }
mikeytown2’s picture

tests fail on my local box

mikeytown2’s picture

added comments & went for option A
I have 5 different ways of creating the temp file; 2 without drupal_tempnam (A & E) 3 with (B, C & D).

mikeytown2’s picture

... can't get this right. Here are all of the D7 done again.

mikeytown2’s picture

FileSize
1.65 KB

Found 2 functions
drupal_dirname
drupal_realpath
I think I can make this work correctly now...

Status: Needs review » Needs work

The last submitted patch, drupal-818818-F-D7.patch, failed testing.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.64 KB

not using drupal_dirname

Status: Needs review » Needs work

The last submitted patch, drupal-818818-G-D7.patch, failed testing.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.64 KB

not using drupal_realpath

mikeytown2’s picture

FileSize
1.76 KB

remove the temp file if rename or copy failed

mikeytown2’s picture

Priority: Normal » Major
andypost’s picture

Why not assign result in copy

++    if ($temp_name && $result = @copy($source, $temp_name)) {
+      // Try the rename opperation
+      if (!$result = @rename($temp_name, $destination)) {
+        // Unlink and try again for windows since rename on windows does not
+        // replace the file if it already exists.
+        @unlink($destination);
+        $result = @rename($temp_name, $destination);
+      }
+    }
-+    else {
-+      $result = FALSE;
-+    }

Is there any benchmarks about copy and rename?

sun.core’s picture

Component: base system » file system
Status: Needs review » Needs work
+++ includes/file.inc	2 Jul 2010 22:32:39 -0000
@@ -677,8 +677,38 @@ function file_unmanaged_copy($source, $d
+  // Perform the replace operation. copy() is not atomic but rename() is.

It's not clear to me what "atomic" means in this context. The further description seems to try to explain parts of it, but overall it looks like the problem space that caused this (ugly) workaround is not properly explained.

+++ includes/file.inc	2 Jul 2010 22:32:39 -0000
@@ -677,8 +677,38 @@ function file_unmanaged_copy($source, $d
+  // Since there could be mutiple processes writing to the same file the best
+  // option is to create a temp file in the same directory and then rename it

1) Typo in "mutiple".

2) "temp" should be "temporary". (and elsewhere)

+++ includes/file.inc	2 Jul 2010 22:32:39 -0000
@@ -677,8 +677,38 @@ function file_unmanaged_copy($source, $d
+    // Get temp filename
...
+    // Place new contents in temp file
...
+      // Try the rename opperation
...
+    // Cleanup tempname if rename/copy failed

1) Missing trailing periods.

2) Typo in "opperation".

Powered by Dreditor.

mikeytown2’s picture

Status: Needs work » Needs review
FileSize
1.92 KB

@sun
Thanks for the review. Latest changes to the code. Hopefully the comments explain the code better, and why it's needed.

@andypost
We don't care if the copy to the temporary file worked; we care if the copy to the destination works. That is why I assigned $result = @rename($temporary_file, $destination) and not do it here @copy($source, $temporary_file)

pillarsdotnet’s picture

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

Bumping and tagging per standard policy.

pillarsdotnet’s picture

Issue tags: -Needs backport to D7

#41: drupal-818818-41-D7.patch queued for re-testing.

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

The last submitted patch, drupal-818818-41-D7.patch, failed testing.

oriol_e9g’s picture

FileSize
1.28 KB

@andypost: Just out of curiosity: copy() / rename() benchmarks in my localhost: Seems that a single copy operation is slower than a rename operation, but a lot of consecutively copy operations are faster O.o

Benchmarks copy() & rename() functions:
-------------------------------------------------------------------------
1 copy operations in 0.0011789798736572 ms.
1 rename operations in 0.00067996978759766 ms.
rename() is 173.38709677419 times faster than copy().
-------------------------------------------------------------------------
10 copy operations in 0.0082869529724121 ms.
10 rename operations in 0.0071439743041992 ms.
rename() is 115.99919903885 times faster than copy().
-------------------------------------------------------------------------
100 copy operations in 0.052769184112549 ms.
100 rename operations in 0.068475961685181 ms.
copy() is 129.76505670266 times faster than rename().
-------------------------------------------------------------------------
1000 copy operations in 0.32604908943176 ms.
1000 rename operations in 1.1666119098663 ms.
copy() is 357.80253577751 times faster than rename().

Code used for benchmarking attached.

oriol_e9g’s picture

Status: Needs work » Needs review
FileSize
1.75 KB

Ported to D8, NR for testbot.

mikeytown2’s picture

#46 rerolled and back ported to D7 & D6

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

The last submitted patch, drupal-818818-47-file-save-D8.patch, failed testing.

mikeytown2’s picture

#47: drupal-818818-47-file-save-D8.patch queued for re-testing.

Got a fatal error on line #37... looks like testbot messed up, so going to retest
Screenshot of testpage: http://drupal.org/node/1158322#comment-6417676

EDIT: Test looks good now :)

mikeytown2’s picture

Version: 8.x-dev » 6.x-dev
FileSize
2.2 KB

Want to make sure patch passes testbot. Fixed 2 errors that I found when back porting the patch to 6.x as drupal_tempnam and drupal_dirname are 7.x+ functions.

mikeytown2’s picture

Version: 6.x-dev » 7.x-dev
FileSize
1.73 KB

Testing the 7.x patch now.

mikeytown2’s picture

Version: 7.x-dev » 8.x-dev
Issue tags: -Needs backport to D7
FileSize
1.75 KB

Moving back to 8.x and waiting...

amontero’s picture

mikeytown2’s picture

Status: Needs review » Closed (duplicate)