-
Notifications
You must be signed in to change notification settings - Fork 11k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[5.7] Fix filesystem locking hangs in PackageManifest::build() #26010
[5.7] Fix filesystem locking hangs in PackageManifest::build() #26010
Conversation
- Filesystem.php 1. Created a new `Filesystem::replace()` method that atomically replaces a file if it already exists. - PackageManifest.php 1. The Filesystem::get() call in PackageManifest::getManifest() no longer has to use an exclusive lock because the packages.php manifest file will always be replaced atomically. 2. Use the new Filesystem::replace() method in PackageManifest::write()
Actually, changing owner won't work anyway if you're not root, so remove that.
So this is put() with some permission checks? Couldn't those permission checks be added to put() instead of introducing a new method? |
No. The It requires the parent directory of the file to be writeable, because it creates a temporary file next to the target file. It also makes sure the permissions of the new file are the same as the original file. |
|
||
if (file_exists($path)) { | ||
// Copy over the permissions and owner from the original file. | ||
chmod($tempPath, (int) base_convert(substr((string) decoct(fileperms($path)), -3), 8, 10)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this fileperms
magic work on windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. The PHP docs on fileperms()
doesn't mention Windows. I'll have to test this on a Windows machine.
If it doesn't, I'll just remove this line. That will make it function a little more different from put()
in the corner case where the permissions of the existing file don't match those of the file it is replaced with. But for the places where we would want to use it it's still appropriate. replace()
isn't a perfect substitute for put()
to begin with.
Perhaps I am just overthinking things and I should remove the chmod()
and chgrp()
altogether and just make it clear replace()
will also replace the ownership and permissions of the file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I did what I was considering; I removed the chmod()
and chgrp()
. Also mentioned what happens to file permissions in the PHPDoc. This also solves any potential Windows incompatibilities.
It really takes all of this to simply replace a file's contents in PHP? We should be running all this code every time we need to do this? I feel like we are adding a bunch of code just to solve an edge case that has never been reported for 2 years until now? |
This "edge case" is new since Laravel 5.6.30, commit 683637a5c0d8ee0049e9067785175f9a291e824e to be exact: To fix race conditions when Exclusive locks aren't supported all file systems, notably NFS3 which is still widely used. In hindsight, since exclusive locks are a new external requirement for Laravel, that fix shouldn't even have been released as a patch update. For more information see issue #25898. I really don't think it's that much code either. There is some sanity checking going on. I'm already considering removing the |
Remove chmod() and chgrp() because it's a bit overkill and probably won't work properly on Windows.
Shower thought: what if locking here is a config setting, default on, which can be turned off, would that work? Then it wouldn't require all this code but simply switched the value of the 3rd arg passed to the get/put method (looking at 683637a which you referenced). Maybe it's a bad either, but it's "simple". |
I thought about that at first, but not sure if it's smart to depend on configuration during the bootstrap process. Of course, you could just default to off, and then carefully check if config is available. Not sure if that would be less new code than what we have in this PR though. The advantage of the |
In my Ubuntu system I have a problem now, every time I launch the composer install command it resets the permissions on the bootstrap/cache/package.php file giving continuous permission denied errors. |
Ah, that sucks. I imagined this might possibly happen to people that symlinked the bootstrap/cache/package.php file. However, I really do think that You might be able to fix this with ACLs. Try: |
You really should have mentioned that in the original PR, so that Taylor could have been aware of all the facts and considered it as part of his decision to merge the PR. |
I did. |
It's not obvious re-reading your text of the potential break. Anyway - regardless - now I'm wondering if the original issue that caused your problems in 5.6.30 should also be reverted. Is there any other way to solve all these problems? |
I think the way to cover all bases is to check for proper permissions of |
What bothers me: how does Laravel ever create the packages.php when it doesn't exist yet, if you don't have write access to the cache dir? |
One possibility would be to replace the
|
@Pillmayr Ah wow that's right. Originally I actually did an additional chmod() to replicate the target packages.php's files to the new file, but that didn't really make a lot of sense because the permissions are also dependant on ownership, so I removed it. I never realised I think the proper fix is to add a chmod() to reset the permissions after the |
To be more precise, tempnam() actually creates the file. So, not as pretty, but adding an |
Did not read the full doc of tempnam and realized afterwards, that the function already creates the file. So you are completely right: changing permissions after creating temp file would be the best solution. |
How about |
Didn't catch your idea first. :D But for me the outcome is the same. |
Using I like that |
Filesystem get was introduced in laravel#25012 as a locking mechanism to prevent partial reads of the manifest file. In laravel#26010 the locking was substituted by an atomic rename-based file write. The get called survived the refactor, resulting a noop file_get_contents that just trashes the output. This commit removes the read entirely. - return $this->manifest = file_exists($this->manifestPath) ? $this->files->getRequire($this->manifestPath) : []; }
Filesystem get was introduced in laravel#25012 as a locking mechanism to prevent partial reads of the manifest file. In laravel#26010 the locking was substituted by an atomic rename-based file write. The get called survived the refactor, resulting a noop file_get_contents that just trashes the output. This commit removes the read entirely.
Filesystem get was introduced in #25012 as a locking mechanism to prevent partial reads of the manifest file. In #26010 the locking was substituted by an atomic rename-based file write. The get called survived the refactor, resulting a noop file_get_contents that just trashes the output. This commit removes the read entirely.
(this time with less code)
See #25898 PackageManifest::build() hangs on filesystems that don't support exclusive file locks
Filesystem.php
Filesystem::replace()
method that atomicallyreplaces a file if it already exists.
PackageManifest.php
The Filesystem::get() call in PackageManifest::getManifest() no
longer has to use an exclusive lock because the packages.php
manifest file will always be replaced atomically.
Use the new Filesystem::replace() method in
PackageManifest::write()