-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Config] Fix slow service discovery for large excluded directories #28200
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
Conversation
@@ -95,6 +99,13 @@ public function getIterator() | |||
|
|||
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) { | |||
foreach (glob($this->prefix.$this->pattern, \defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) { | |||
if (is_dir($path) && $this->forExclusion) { |
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.
if (is_dir($path)) {
if ($this->forExclusion) {
yield $path => new \SplFileInfo($path);
continue;
}
if (array_key_exists($path, $this->excludedPrefixes)) {
continue;
}
}
continue; | ||
} | ||
|
||
if (array_key_exists($path, $this->excludedPrefixes)) { |
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.
this requires normalizing the directory separators in the path before using it as a key, as done in FileLoader
public function testIteratorSkipsFoldersForGivenExcludedPrefixes() | ||
{ | ||
$dir = \dirname(__DIR__).\DIRECTORY_SEPARATOR.'Fixtures'; | ||
$resource = new GlobResource($dir, '/*Exclude*', true, false, array($dir.\DIRECTORY_SEPARATOR.'Exclude' => true)); |
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.
please also add a test using /
in the exclude path rather than \DIRECTORY_SEPARATOR
, to ensure it works. It will be quite common for the config files to use /
all the time (much easier in XML or YAML)
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.
Done.
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.
Thanks for working on this!
Tests are failing right now, could you have a look?
continue; | ||
} | ||
|
||
if (array_key_exists(str_replace('\\', '/', $path), $this->excludedPrefixes)) { |
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.
Is the str_replace needed for Windows? If yes, I'd suggest to use str_replace(\DIRECTORY_SEPARATOR, '/', $path)
instead.
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.
Check @stof comment #28200 (comment)
So I just copied how it's done in https://github.com/symfony/dependency-injection/blob/master/Loader/FileLoader.php#L117
Do you still want me to change it?
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.
yes please, \
is a legal character in a path on Linux (even though unusual of course)
that line could be changed also for sure
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.
Ok, should I also do another PR to change https://github.com/symfony/dependency-injection/blob/master/Loader/FileLoader.php#L117 too?
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.
if you're up to yes :) please check the full code base in case there are any other places that need such change (on branch 2.8 since it's the lowest maintained branch)
Tests are fine in unix, will check on windows. |
Not on Travis, see e.g. https://travis-ci.org/symfony/symfony/jobs/415858572 |
It's complaining about something I haven't changed |
This test is directly related actually. We need to figure out why and how. But that's not a glitch nor a transient test. Only this PR has it for now. |
@gonzalovilaseca Do you need help here? |
@fabpot Sorry, got distracted with other friends projects. I'm resuming this today, managed to debug it but haven't found the issue yet. Will ask for help if I can't fix it in the following days. |
PR updated! |
@@ -95,11 +99,27 @@ public function getIterator() | |||
|
|||
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) { | |||
foreach (glob($this->prefix.$this->pattern, \defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) { | |||
if (is_dir($path)) { | |||
if ($this->forExclusion && \in_array($path, glob($this->prefix.$this->pattern, GLOB_BRACE | GLOB_ONLYDIR))) { |
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.
GLOB_BRACE should be use conditionally as done just above
in_array should also be provided with its 3rd argument (true = strict)
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.
What's the deal with GLOB_BRACE
? Is it because The GLOB_BRACE flag is not available on some non GNU systems, like Solaris.
?
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.
Correct.
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.
But then, shouldn't we throw an error message if the {
is in the pattern
and GLOB_BRACE
is not defined?
Otherwise it won't behave as expected and user won't be aware of it.
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.
That's already dealt with, see L100. But this also means we might have forgotten the case below this "if" that falls back to using Finder. Does it need to know about excluded paths also?
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.
Initially it wasn't aware of excluded prefixes, but then it made some tests fail eg:
src/Symfony/Component/DependencyInjection/Tests/Loader/YamlFileLoaderTest.php
for testPrototype
.
In that test '../Prototype/{OtherDir,BadClasses}'
is excluded, so on first foreach loop as src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype
is not in excluded prefixes, it gets to line 113, so anything under that folder is not longer parsed by the initial glob
foreach, but by the recursive iterator.
It's taken me quite a lot of time to figure all this out, in fact this class is getting quite complex, so maybe it's worth a try to refactor it into something easier to follow?
@@ -132,7 +152,7 @@ function (\SplFileInfo $file) { return '.' !== $file->getBasename()[0]; } | |||
|
|||
$prefixLen = \strlen($this->prefix); | |||
foreach ($finder->followLinks()->sortByName()->in($this->prefix) as $path => $info) { | |||
if (preg_match($regex, substr('\\' === \DIRECTORY_SEPARATOR ? str_replace('\\', '/', $path) : $path, $prefixLen)) && $info->isFile()) { | |||
if (\preg_match($regex, \substr('\\' === \DIRECTORY_SEPARATOR ? \str_replace('\\', '/', $path) : $path, $prefixLen)) && $info->isFile()) { |
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.
changes to be reverted
@@ -95,11 +99,27 @@ public function getIterator() | |||
|
|||
if (0 !== strpos($this->prefix, 'phar://') && false === strpos($this->pattern, '/**/') && (\defined('GLOB_BRACE') || false === strpos($this->pattern, '{'))) { | |||
foreach (glob($this->prefix.$this->pattern, \defined('GLOB_BRACE') ? GLOB_BRACE : 0) as $path) { | |||
if (is_dir($path)) { | |||
if ($this->forExclusion && \in_array($path, glob($this->prefix.$this->pattern, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR), true)) { |
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.
Shouldn't this glob be moved out of the foreach ?
continue; | ||
} | ||
|
||
if (array_key_exists(str_replace(\DIRECTORY_SEPARATOR, '/', $path), $this->excludedPrefixes)) { |
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.
You can replace these with issets, will be faster. Map of values has been reversed (and true
used for value) for precisely this reason.
OR we can use \in_array
which has been optimized into opcode in PHP 7.2 already and is even faster than isset, but you need to reverse the map. It's good opportunity to do this now, because as you added that array into constructor of this class, we won't be able to reverse it later without breaking BC. Obvious con is it will make code slightly slower in PHP 7.1. But IMO worth it, as it's only one PHP version and all future versions will be faster with in_array
, plus it's more readable (intent is more obvious). WDYT @nicolas-grekas ?
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.
in_array cannot be faster than isset, because it still has to do a table scan vs an O(1) hashmap lookup for isset, isn't it?
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.
It will reverse it internally, see https://phpinternals.net/articles/optimising_internal_functions_via_new_opcode_instructions#the-in_array-function
I’m happy to change it to what both of you decide |
@nicolas-grekas @stof Can you have nother look please? |
} | ||
), | ||
\RecursiveIteratorIterator::LEAVES_ONLY | ||
)); |
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.
this whole block has one extra indentation level that should be removed.
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.
(Thanks! FYI, I push-forced some changes on your fork)
Thxs! |
Thank you @gonzalovilaseca. |
…irectories (gonzalovilaseca) This PR was squashed before being merged into the 4.2-dev branch (closes #28200). Discussion ---------- [Config] Fix slow service discovery for large excluded directories | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | no | Fixed tickets | #26736 | License | MIT | Doc PR | Not sure if this is a bug fix or not, is more an improvement. Please for all detail follow the conversation here: #26736 Commits ------- fa731e5 [Config] Fix slow service discovery for large excluded directories
This PR was squashed before being merged into the 4.2-dev branch (closes #29062). Discussion ---------- Fix GlobResource serialization | Q | A | ------------- | --- | Branch? | master <!-- see below --> | Bug fix? | yes | New feature? | no <!-- don't forget to update src/**/CHANGELOG.md files --> | BC breaks? | no <!-- see https://symfony.com/bc --> | Deprecations? | no <!-- don't forget to update UPGRADE-*.md and src/**/CHANGELOG.md files --> | Tests pass? | no <!-- please add some, will be required by reviewers --> | Fixed tickets | #28200 <!-- #-prefixed issue number(s), if any --> | License | MIT | Doc PR | <!-- required for new features --> Since @gonzalovilaseca improvement in PR #28200, I noticed that the container is being compiled systematically. This is caused by the two added properties (`forExclusion`, `excludedPrefixes`) not being serialized, and lead to wrong hash computing. I updated the `serialize` and `unserialize` methods in this PR. ping @nicolas-grekas Commits ------- 6ce7f07 Fix GlobResource serialization
…-grekas) This PR was merged into the 4.2 branch. Discussion ---------- [Config] fix path exclusion during glob discovery | Q | A | ------------- | --- | Branch? | 4.2 | Bug fix? | yes | New feature? | - | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Something we missed in #28200 - reported on Slack. Commits ------- 4ada4dc [Config] fix path exclusion during glob discovery
Not sure if this is a bug fix or not, is more an improvement.
Please for all detail follow the conversation here:
#26736