Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[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

Merged
merged 1 commit into from
Oct 23, 2018
Merged

[Config] Fix slow service discovery for large excluded directories #28200

merged 1 commit into from
Oct 23, 2018

Conversation

gonzalovilaseca
Copy link
Contributor

@gonzalovilaseca gonzalovilaseca commented Aug 14, 2018

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

@@ -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) {
Copy link
Contributor

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)) {
Copy link
Member

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));
Copy link
Member

@stof stof Aug 14, 2018

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

@nicolas-grekas nicolas-grekas left a 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)) {
Copy link
Member

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.

Copy link
Contributor Author

@gonzalovilaseca gonzalovilaseca Aug 14, 2018

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?

Copy link
Member

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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)

@gonzalovilaseca
Copy link
Contributor Author

Tests are fine in unix, will check on windows.

@nicolas-grekas
Copy link
Member

Tests are fine in unix, will check on windows.

Not on Travis, see e.g. https://travis-ci.org/symfony/symfony/jobs/415858572

@gonzalovilaseca
Copy link
Contributor Author

It's complaining about something I haven't changed PHP Fatal error: Class 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\BadClasses\MissingClass' not found in /home/travis/build/symfony/symfony/src/Symfony/Component/DependencyInjection/Tests/Fixtures/Prototype/BadClasses/MissingParent.php on line 5
Will check anyway but might be a glitch.

@nicolas-grekas
Copy link
Member

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.

@nicolas-grekas nicolas-grekas added this to the next milestone Aug 19, 2018
@fabpot
Copy link
Member

fabpot commented Sep 4, 2018

@gonzalovilaseca Do you need help here?

@gonzalovilaseca
Copy link
Contributor Author

@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.
Thanks though!

@gonzalovilaseca
Copy link
Contributor Author

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))) {
Copy link
Member

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)

Copy link
Contributor Author

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. ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.

Copy link
Contributor Author

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.

Copy link
Member

@nicolas-grekas nicolas-grekas Sep 30, 2018

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?

Copy link
Contributor Author

@gonzalovilaseca gonzalovilaseca Sep 30, 2018

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()) {
Copy link
Member

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)) {
Copy link
Member

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)) {
Copy link
Contributor

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 ?

Copy link
Member

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gonzalovilaseca
Copy link
Contributor Author

I’m happy to change it to what both of you decide

@gonzalovilaseca
Copy link
Contributor Author

@nicolas-grekas @stof Can you have nother look please?

}
),
\RecursiveIteratorIterator::LEAVES_ONLY
));
Copy link
Member

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.

@nicolas-grekas nicolas-grekas changed the title Autowiring is very slow for large excluded directories [Config] Fix slow service discovery for large excluded directories Oct 11, 2018
Copy link
Member

@nicolas-grekas nicolas-grekas left a 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)

@gonzalovilaseca
Copy link
Contributor Author

Thxs!

@nicolas-grekas nicolas-grekas modified the milestones: next, 4.2 Oct 20, 2018
@nicolas-grekas
Copy link
Member

Thank you @gonzalovilaseca.

@nicolas-grekas nicolas-grekas merged commit fa731e5 into symfony:master Oct 23, 2018
nicolas-grekas added a commit that referenced this pull request Oct 23, 2018
…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
nicolas-grekas added a commit that referenced this pull request Nov 2, 2018
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
nicolas-grekas added a commit that referenced this pull request Dec 1, 2018
…-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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants