Problem/Motivation
With reasonable load from parallel requests it is possible for the time between is_dir()
and mkdir()
to allow for the creation of the directory causing a false error message.
Proposed resolution
Hide/ignore php's warnings from mkdir()
and provide our on explicit tests for failure conditions we care about.
Remaining tasks
Test if possible.
User interface changes
None
API changes
None
Data model changes
None
Problem: due to a race condition it can happen that a mkdir throws a warning when creating a twig cache file.
Proposed solution: since using semaphores would be rather heavy it's suggested to silence the warning (using @mkdir).
Original report:
i saw this error
Warning: mkdir(): File exists in Drupal\Component\PhpStorage\FileStorage->createDirectory() (line 170 of core/lib/Drupal/Component/PhpStorage/FileStorage.php).
I edited and turned on a node.
then I click the taxonomy term link at the node.
at the taxonomy page by the link i saw this message.
when re-entering the page - no error was there.
Comment | File | Size | Author |
---|---|---|---|
#75 | warning_mkdir_file-2685957-75.do-not-test.patch | 4.04 KB | pguillard |
#72 | warning_mkdir_file-2685957-72.patch | 4.07 KB | neclimdul |
#72 | warning_mkdir_file-2685957-72.interdiff.txt | 713 bytes | neclimdul |
Comments
Comment #2
madan879 CreditAttribution: madan879 at Clarion Technologies commentedHello,
As you mentioned scenario, I have checked but I am not getting any errors. Otherwise please explain little bit more.. that will helpful to identify exact things.
Comment #3
fomenkoandrey CreditAttribution: fomenkoandrey commentedI've seen this mistake once.
I received it - wrote in the first message.
more than reproduce it failed :(
Comment #4
dawehnerIt would be really helpful if you could provide a backtrace
Comment #5
fomenkoandrey CreditAttribution: fomenkoandrey commentedI will, if you explain step by step how to do a backtrace.
(If I understood you correctly)
Comment #6
emena CreditAttribution: emena commentedI have the same problem after doing "drush8 pm-updatecode" in my test site:
Comment #7
fomenkoandrey CreditAttribution: fomenkoandrey commenteddrupal 8.1.0
cleared cache, refreshed the main page of the site (F5) and got the error:
when I updated the page one more time- no error.
i try re clearing cache and refresh the page - no error.
some floating bug.
Comment #8
zach.bimson CreditAttribution: zach.bimson commentedIm also experiencing this issue... Haven't nailed down the cause yet as its pretty random but +1 as it is a thing.
Comment #9
freelockWe are hitting this error when sites/default/files is a symlink, and not a directory.
We do this on developer machines -- mount an asset directory over the network from a central file server, and then symlink sites/default/files in developer copies to the shared network asset mount.
Because this method is using is_dir() to check for the presence of the file path, this fails the is_dir() test and then throws an exception when it attempts to create a directory in a path where there's already a symlink.
I think this is a common configuration -- we find instructions from Acquia for configuring this way. Seems like a missing configuration option?
P.S. I don't know if this is the OP's original issue or not, but it's the same error, same code.
Comment #10
SteffenRI just stumpled upon the same error while opening the entity_browser modal for the first time after a clean installation / clearing the caches.
While opening the modal for a second time everything is working fine.
Maybe we should check for the existence of the directory that's going to be created in createDirectory method of FileStorage class.
Comment #11
donavanwilliams CreditAttribution: donavanwilliams as a volunteer commented@freelock hit it on the nose in Comment #9:
I'm in the same situation. Simlink used for the files directory. Same error. Thoughts?
Comment #12
donavanwilliams CreditAttribution: donavanwilliams as a volunteer commentedFollowing up on my comment #11
It looks like I solved this on my end. My simlink was pointing to an invalid location. Once I fixed the simlink to the proper location, I got it to work without that error. My mistake.
Comment #13
Saphyel CreditAttribution: Saphyel as a volunteer and at Comic Relief commentedStill happening I think is because is executing twice some command but I can't replicate it ... I don't know when happen but is harmless
Comment #14
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedComment #15
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedThe error occurs when twig classes are generated:
Drupal\Component\PhpStorage\FileStorage->createDirectory('sites/default/files/php/twig/e2c654aa_status-messages.html.twig_c0aba12839784fc81b2429bd08d64d5d5151def5936159ad900057dd21493350', 511) (Line: 128)
Drupal\Component\PhpStorage\FileStorage->ensureDirectory('sites/default/files/php/twig/e2c654aa_status-messages.html.twig_c0aba12839784fc81b2429bd08d64d5d5151def5936159ad900057dd21493350') (Line: 84)
Drupal\Component\PhpStorage\MTimeProtectedFastFileStorage->save('e2c654aa_status-messages.html.twig_c0aba12839784fc81b2429bd08d64d5d5151def5936159ad900057dd21493350', '<?php
/* core/themes/bartik/templates/status-messages.html.twig */
class __TwigTemplate_a9ae7c2370f62c93405327a1077d9b0d9d55a31195f3bf91e6044f657d64c27a extends Twig_Template
{
...
Comment #16
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedThe symlink directory was owned by the wrong user, properly chmodding it seems to have fixed it:
chown -h www-data:www-data files
Comment #17
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedI spoke too soon, the issue persists.
Comment #18
ivavictoriaI saw this warning today and while I don't know anything about it, I thought I'd report what I know in case it's helpful:
I noticed the warning in the dblog after upgrading a site to 8.2.1. It occurred on the first page I visited after upgrading: /user/login
The site is hosted on Pantheon, and the warning appeared in the dev instance of the site, but not in test or live. Perhaps this indicates that this is some sort of permissions error?
Comment #19
ckaotikI've also run into this error repeatedly, but not repeatable. Unfortunately :( It does seem somewhat related to Twig templates in debug mode (
development.services.yml
withdebug: true
,auto_reload: null
,cache: true
), and frequently occurs after a cache rebuild (drush cr
).Here's more of the error message, in case that helps:
Comment #20
fomenkoandrey CreditAttribution: fomenkoandrey commentedi have this error on drupal 8.2.1
Comment #21
bmcclure CreditAttribution: bmcclure as a volunteer and at Top Floor commentedI also have this issue. I've had it for longer than just Drupal 8.2.1, but I don't recall exactly when it started.
Occasionally, like 2 or 3 times per day, I see this error on my site. It's usually directly after login, however it'll also happen the first time I visit a page on the site in the morning, even though I'm still logged in from the day before.
This seems to indicate to me it's somehow related to twig cache expiration--as if twig is trying to regenerate a template whose cache has expired and is unable to overwrite the existing file. In my case, I believe the error always traces back to the block template, but I haven't entirely figured out if it's actually related to a particular block or not.
Comment #22
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedComment #23
mpp CreditAttribution: mpp as a volunteer and at AmeXio commented@ #9, I don't think the issue is
is_dir
, that should work properly on a symlink (See http://php.net/manual/en/function.is-dir.php#118994).The problem might be in
dirname
on line 162:dirname returns the symlink path (See http://stackoverflow.com/questions/12580330/php-dirname-returns-symlink-...). So we should probably change that for symlinks to:
A quick workaround would be to suppress warnings with @mkdir:
Comment #24
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedComment #25
bmcclure CreditAttribution: bmcclure as a volunteer and at Top Floor commentedThanks!
I realize this is an 8.3.x-dev patch, but it applied cleanly on my 8.2.2 installation as the code it's changing seems to be identical, and I haven't seen another of these errors yet (though enough time may not have passed yet for a true test in my case)
I'll report back if I experience any further instances of this error using this method of fixing it.
Comment #26
aimevpI'm not sure if it's related but I had a very similar error after installing the Libraries module:
Warning: mkdir(): File exists in Drupal\Core\File\FileSystem->mkdirCall() (line 237 of core/lib/Drupal/Core/File/FileSystem.php).
I've also had the error this issue is about numerous times so I applied the patch mentioned in #23. At first sight I'm not getting the error anymore. Also not when I uninstall/install the Libraries module. Should something change I'll let it know.
Comment #27
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedAdded the @ignore errors for testing purpose.
Comment #28
Saphyel CreditAttribution: Saphyel as a volunteer and at Comic Relief commented@mpp that is ok.. but I don't think the answer is suppress the error message..., should be check if exist and if doesn't mkdir
Comment #29
mpp CreditAttribution: mpp as a volunteer and at AmeXio commented@saphyel, have a look at #23, may need some work though.
Comment #30
bmcclure CreditAttribution: bmcclure as a volunteer and at Top Floor commentedI'm still experiencing this error with the patch in #23 applied. It *might* be less often, but it still seems like every time I come back to the site after having been away for a while, I see one of these messages once still even with the patch.
I'm going to try #27 in the hopes it'll at least get this message to stop annoying our client for now, but I'll happily test another potential fix if posted.
Comment #31
pingwin4egI have the same warning on my local dev site, but I don't use symlinks.
I don't know how to reproduce it exactly, but I'll try to explain conditions of how it occurs:
My guess of the problem cause is the following:
is_dir($directory)
returns FALSE. One of requests actually creates that dir, so other requests callmkdir($directory)
when the dir is already there.Why do I think there are multiple requests on a single page? In DBLog list I can see "Attempting to re-run cron while it is already running" multiple times between cron messages about "Starting execution of some_cron(), execution of another_cron() took some ms."
Here are some example URLs that are being requested along with the page itself:
Comment #33
cilefen CreditAttribution: cilefen commented@alexpott, @catch, @cottser, @xjm and I considered this issue and agree this is suitably a major priority bug because it is policy that PHP errors triggered in the UI are major.
Comment #34
bluccas CreditAttribution: bluccas commentedI tested the 2685957-27.patch sent by @mpp in the 8.2.7 core version.
The warning was fixed.
Comment #35
bluccas CreditAttribution: bluccas commentedI tested the 2685957-27.patch sent by @mpp in the 8.2.7 core version.
The warning was fixed.
Comment #36
pingwin4eg@bluccas That's not a fix actually, but a "closing-eyes" workaround. The patch just suppresses the warning, not fixes it.
I'm changing priority back to Major, and setting status to Needs Work as per my comment #31 above.
Comment #37
cilefen CreditAttribution: cilefen commented@bluccas Welcome to the team!
Please do not change issue priority on triaged issues. Read about issue priority and reviewing issues.
Comment #38
mpp CreditAttribution: mpp as a volunteer and at AmeXio commented@pingwin4eg: agreed that it is a patch and not a solution. However, there may always be two concurrent requests that try to check if there's a directory and try to create it. So this seems to be a race condition: both may detect no directory exists but only one of them will be able to create it.
Solving the race condition would involve a semaphore in Drupal, which imo seems quite heavy for what is happening here.
Comment #39
pingwin4egYes, that's what I stated earlier. But using @ is a bad practice. It can lead to other "hidden" bugs, that would be hard to find.
In this specific situation the @ seems harmless, but... I don't know, it's kind of a bad feeling.
Ok, let's change the status to Needs Review to attract attention of other people to the issue. Maybe someone can propose a brilliant solution that will satisfy all of us.
Comment #40
pingwin4egP.S.: summary needs an update.
Comment #41
mxhConsidering the purpose of this cache backend, I'd actually vote for #27. Race conditions should be tolerable regards anything which is appropriate to be handled by a filesystem storage. AFAIK this problem won't be avoidable without eliminating the benefits of this cache backend.
Comment #42
mxhI don't see a reason why this needs to wait until 8.4.x
Comment #43
mpp CreditAttribution: mpp as a volunteer and at AmeXio commentedComment #45
neclimdul#23 is correct re: symlinks with one exception, we don't need any special code because what that's saying is that when 'sites/default/files' is a symlink and
is_dir('sites/default/files');
is run, its going to to resolve that to '/srv/myfiles' or where ever and say "oh, yeah that's a directory" and return true.The problem is as others have suggested the race between requests checking and trying to make the directory which is why the error is on line 170 and not further down on
Considering that we care about one failure and not the other the return value from mkdir actually becomes useless and we can simplify this function quite a bit and move all the code to the tail recursion (optimistically avoiding recursion) and only setup the mkdir/chmod logic once.
The patch makes the race tighter(the time between the is_dir and fileperms call) and the only side effect will be an additional chmod which might be a small disk write cost but no user impact.
Comment #46
neclimdulself review, that documentation is a little wonky. Probably from copy and pasting from previous code.
Comment #47
Stefdewa CreditAttribution: Stefdewa at Calibrate commentedTested patch from #46 successfully on core 8.3.6.
Comment #48
larowlannit: could already exist - can be fixed on commit.
Bug fixes normally come with a failing test - is there a way we can reproduce this scenario in a test?
Comment #49
mpp CreditAttribution: mpp as a volunteer and at AmeXio commented@larowalan, a test would be awesome but do you know of any single race condition test in Drupal, or php for that matter?
Comment #50
larowlanI'm asking did we actually find the conditions under which the race occurred and can we simulate it with vfsstream
Comment #51
codesmithOne of my D8.3.7 sites on Pantheon is getting this error one to eight times per minute. I applied the patch in #46 and so far so good on the errors. Although I'm concerned about why the Twig cache is going so nuts.
Comment #52
neclimdulI'm not sure how we would change the file structure in the middle of a method call in a test. That seems impossible.
Comment #53
neclimdulTo clarify, the error is that this method has a is_dir check, a parent directory check, then a mkdir. the problem is that in that small snippet of code the directory can come into existence and the mkdir fails loudly. Technically with heavy load just the time between is_dir and mkdir side by side could allow for the bug.
I've skimmed the vfsstream documentation and confirmed that if there is a way to respond to fileystem events and modify it I don't see it which makes this impossible to consistently test.
Comment #54
larowlanThanks @neclimdul - appreciate the effort going into investigation and updating issue summary
Comment #55
mpp CreditAttribution: mpp commentedThe only way to change the file structure in the middle of the method would be by using a Mock object. To Mock you need objects but the code is using php native file functions and unfortunately Php doesn't foresee a native file system object like asp or many other languages and using a new dependency just to make this piece of code testable for race conditions would come at a performance cost.
Comment #56
neclimdulI guess this is the only thing keeping it from RTBC?
Comment #57
mpp CreditAttribution: mpp commentedThanks!
Comment #58
alexpottThis looks like a good way to reduce the possibility of the race from happening.
However, I think we have a problem. Not everywhere uses the return value of this method - so before we would have had a message now we might not. And we would have stopped execution but now we are going to continue on regardless.
I think we should throw an exception in \Drupal\Component\PhpStorage\FileStorage::ensureDirectory() if we have not ensured the directory. And we should try to add tests around what happens when \Drupal\Component\PhpStorage\FileStorage::createDirectory() fails as we don't have any test coverage of that.
Comment #59
neclimdulCurrently createDirectory does not actually stop execution so throwing an exception could be a pretty big BC break. It actually emits an E_WARNING which manifests in this bug as a DSM of the error or watchdog depending on your error handling levels. I believe I have a screenshot of the DSM from a development environment somewhere I can upload if its relevant but put the message in the summary in red text at the top of the page and you've got it.
But if we consider the warning a byproduct of the method we need we can add a trigger_error before the
return false;
. That does seem reasonable.Comment #60
alexpott@neclimdul ah how right you are...
So a warning is not going to stop execution.
Nice idea about triggering a warning if we return FALSE from here. +1
Comment #61
neclimdulWarning and test of the warning. I tried to test the return of createDirectory but trigger_error still outputs text so even disabling phpunit's exceptions temporarily and that doesn't work. I think we're good though because we know that the warning and the return type are connected.
Comment #62
alexpottWell we don't know why it has failed so saying permission denied seems presumptive - maybe just
'mkdir(): Failed'
- I thought about including the directory name but that's not what PHP does and I guess it could result in information disclosure.Comment #63
mpp CreditAttribution: mpp commented@neclimdul, I didn't know about the vfsStream package, nice one!
Comment #64
neclimdulSo technically true but I went off the PHP documentation:
Since we're handling the existing directory cases, (recursively creating and hiding the existing directory failure). The permission warning seemed the only relevant failure case. I think what that means from PHP's perspective is more of "Either permission error or something that looked like a permission error." I'm open to changing it if that doesn't make sense though.
@mpp Thanks! You might be using it already since its hidden through out core's test suite. It is also what makes the rest of the PhpStorage file storage test work. :-D
Comment #65
mpp CreditAttribution: mpp commentedIndeed, good to know :-)
In the context of this issue I assume the error could also be that the directory already exists (although chances are very small now the risk of the race condition is a lot smaller).
Comment #66
neclimdulNoticed some coding standards problems. Must have been working a little too fast.
Alternate version of patch with "mkdir(): Failed" warning text as well.
Comment #67
neclimdulThe if
will lead to a true statement in the case that the directory already exists so we won't trigger the warning ;)
Comment #68
mpp CreditAttribution: mpp commentedIn theory the if could result false for two concurrent requests in which case the mkdir will fail for one of them.
This was the original problem :-) But I said "could be" and "in theory" as the time between the if and the mkdir has become smaller. But it remains "possible", especially if there's a lot of recursion (subdirectories that need to be created).
I'd consider this issue tackled!
Comment #69
alexpottThe thing is we don't know if it is the
mkdir()
that has caused this - I think it could be the chmod when creating a parent dir - so perhaps it is best just to say:\Drupal\Component\PhpStorage\FileStorage::createDirectory(): Failed
because that is all we can be sure of.Comment #70
neclimdulThe if is after the mkdir now so when mkdir fails in a concurrent storm it still looks like it succeeds. The only issues remain around chmod and I think we're safe there because as long as you have write permissions you can chmod repeatedly without error.
I see what you're saying, I was focused on the block doing the work and not the recursion where the chmod could cause a failure. I'll move the warning next to the mkdir so it matches the current behavior better and doesn't report on chmod.
Comment #71
neclimdulForgot this needed a reroll to move the error. Fixing the status to reflect that.
Comment #72
neclimdulSorry for the delay. For your reviewing pleasure.
Comment #73
alexpottThis looks great. The patch reduces the possibility of a race and if there is one then the warnings are silenced because there is no real problem.
Comment #74
mpp CreditAttribution: mpp at AmeXio for District09 commentedAgreed with #73.
Comment #75
pguillard CreditAttribution: pguillard commentedFor those -like me- who need to apply this patch on the 8.3 branch (at least on release 8.3.7), here is the patch.
Comment #77
larowlanfails are in HEAD
Comment #78
catchnit: try to.
Comment #81
catchFixed that on commit.
Committed/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!
Comment #82
neclimdulThanks!
Comment #84
jonathan1055 CreditAttribution: jonathan1055 as a volunteer commentedI have had a recurrence of this problem. Not sure about the etiquette of posting details on this closed issue, so I have posted to the open issue #2845122-7: mkdir(): File exists in BrowserHtmlDebugTrait.php:141 during BrowserTestBase instead.
Comment #85
Sweetchuck