Problem/Motivation

Per @phenaproxima, we only use symfony/finder for convenience and symfony/config for autowiring:

adam.hoenich: My preference would actually be to remove the dependency on symfony/finder. We currently depend on it for convenience. With a little work I think we could refactor it out
wim.leers: ah!
adam.hoenich: And maybe even symfony/config too, if we can find a way to load in Composer Stager and its autowiring
wim.leers: is that what it’s for?!
adam.hoenich: It is.
wim.leers: lol
wim.leers: okay
adam.hoenich: If you look at PackageManagerServiceProvider, it’s the only way I could expose all those classes to the container. It needed things that exist in symfony/config

We should verify that we need these dependencies. A whole new dependency for Drupal core/AU is not worth it if it just saves a few LoC.

Steps to reproduce

Proposed resolution

  1. Remove dependency on symfony/finder
  2. Add symfony/config as a core dependency per #11: #3326246: Add symfony/config to core's dependencies for package_manager

Remaining tasks

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Title: Attempt to remove dependency on symfony/finder and symfony/config » Attempt to remove dependency on symfony/finder and symfony/config or document why we need them
wim leers’s picture

wim leers’s picture

Title: Attempt to remove dependency on symfony/finder and symfony/config or document why we need them » Attempt to remove dependency on symfony/config or document why we need them
Related issues: +#3031379: Add a new test type to do real update testing

symfony/finder actually is already required by Drupal core since #3031379: Add a new test type to do real update testing and 8.8.0 … so nothing to do there! 🥳

phenaproxima’s picture

Sorry to be the bearer of bad news, but that is, as far as I know, a dev dependency of core, not a runtime dependency. So I think we have two options:

  1. Ask core to move the dependency from dev to runtime. IMHO this is less optimal, as it would be preferable to avoid adding runtime dependencies unless we really need them.
  2. Stay the course and refactor out our need for it.
phenaproxima’s picture

Title: Attempt to remove dependency on symfony/config or document why we need them » Attempt to remove dependency on symfony/config and symfony/finder, or document why we need them
wim leers’s picture

#7: D'oh, great point — I missed that! 🙈 Thanks! 🙏

tedbow’s picture

Status: Active » Needs work
wim leers’s picture

namespace Symfony\Component\DependencyInjection\Loader;

[…]

class DirectoryLoader extends FileLoader

+

namespace Symfony\Component\DependencyInjection\Loader;

[…]

use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader;

[…]

abstract class FileLoader extends BaseFileLoader

+

namespace Symfony\Component\Config\Loader;

[…]

/**
 * FileLoader is the abstract class used by all built-in loaders that are file based.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class FileLoader extends Loader

→ I don't see how we can avoid using symfony/config if we don't want to make \Drupal\package_manager\PackageManagerServiceProvider::register() a lot more complicated.

https://symfony.com/doc/current/components/config.html says:

The Config component provides several classes to help you find, load, combine, fill and validate configuration values of any kind, whatever their source may be (YAML, XML, INI files, or for instance a database).

… which sounds like a reasonable component to include in Drupal core.

wim leers’s picture

symfony/finder is used in:

  • \Drupal\package_manager\PathExcluder\GitExcluder
  • \Drupal\package_manager\PathExcluder\NodeModulesExcluder
  • \Drupal\package_manager\Validator\DuplicateInfoFileValidator
  • … as well as in a number of tests

I'm tempted to say that that code clarity is worth it.

But Drupal also has a drupal/core-filesystem component, which has a pretty capable \Drupal\Component\FileSystem\RegexDirectoryIterator. Trying to convert to that…

wim leers’s picture

Been fighting \RecursiveFilterIterator and friends, getting there. Should be able to wrap this up tomorrow.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
wim leers’s picture

Title: Attempt to remove dependency on symfony/config and symfony/finder, or document why we need them » Remove dependency on symfony/finder, document why we need symfony/config
Issue summary: View changes
Related issues: +#3326246: Add symfony/config to core's dependencies for package_manager
wim leers’s picture

wim leers’s picture

Assigned: Unassigned » tedbow

Merged in the upstream changes from the past ~month.

Explicitly assigning to @tedbow to (finally 😅) get feedback.

I still cannot reproduce the DrupalCI-only failures locally. Here's hoping that was an ephemeral problem on the DrupalCI infrastructure … 🤞

wim leers’s picture

It clearly isn't ephemeral. @tedbow: can you reproduce the failures locally?

tedbow’s picture

Assigned: tedbow » Unassigned
Status: Needs review » Needs work

I checked some of the test failures and they pass for me locally

@Wim Leers Just to be clear you didn't manually abort any of these test runs, correct? They just timed out?

I see this in the console output for all the ones I have checked so far

12:39:17 Build timed out (after 110 minutes). Marking the build as aborted.
13:00:16 Build was aborted

Which is strange that we would get a bunch of un-reproducible test fails and a time out.

since \Drupal\package_manager\Validator\DuplicateInfoFileValidator::findInfoFiles and similar methods scan the entire codebase for particular files could the replacement to symfony/finder be that much worse on performance that it causes this timeouts and test failures?

tedbow’s picture

I tried \Drupal\package_manager\Validator\DuplicateInfoFileValidator::findInfoFiles locally and it seemed not using symfony/finder was actually faster so maybe something with drupalci makes it slower?

tedbow’s picture

ok the latest test failure 3321933-performance-test I proves that using symfony/finder is for some reason much slower on drupalci

1) Drupal\Tests\automatic_updates\Unit\PerformanceTest::testPerformance
Failed asserting that 20.101731061935425 is less than 0.6194238662719727.

/var/www/html/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php:122
/var/www/html/vendor/phpunit/phpunit/src/Framework/Constraint/Constraint.php:55
/var/www/html/modules/contrib/automatic_updates/tests/src/Unit/PerformanceTest.php:49
/var/www/html/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
/var/www/html/vendor/phpunit/phpunit/src/Framework/TestSuite.php:673
/var/www/html/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:661
/var/www/html/vendor/phpunit/phpunit/src/TextUI/Command.php:144
/var/www/html/vendor/phpunit/phpunit/src/TextUI/Command.php:97

tedbow’s picture

Pushed up a version with data provider to run the test multiple times

My guess is either:

  1. Some on drupalci make using symfony finder much faster
  2. Or something about updated version of findInfoFiles causes it to not ignore a bunch of files that previous ignored and those conditions only happen on drupalci. like maybe it following some symlink to very large directory
tedbow’s picture

as a try to exclude #23.2 I pushed up a change ensure that the methods find the same files.

As way determine if #23.1 is correct someone could update \Drupal\Tests\automatic_updates\Unit\PerformanceTest::testPerformance to programmatically create folder with say 1000 files in setUp() and then use that as the directory to scan. Then we could tell if the just given a known folder with a lot of files if the symfony/finder method was just dramatically faster on drupalci for some reason.

I am not going to work on this right now if anyone else wants to try this

wim leers’s picture

Assigned: Unassigned » wim leers

Thanks, great research, @tedbow! 👍

After discussing with @tedbow, I have some ideas on how to move forward here now — there likely is a lot of additional directories on DrupalCI that we don't have locally, but we don't know where yet 🫣

wim leers’s picture

Now that #3328742: phpcs stopped working since the switch to testing on Drupal 10.0.x by default is finally done, this is the next most important issue! Will continue this tomorrow.

omkar.podey’s picture

Assigned: wim leers » omkar.podey
omkar.podey’s picture

Drupal CI behaves weirdly , does not run tests according to drupalci.yml , hence the retests.

omkar.podey’s picture

Test times

New PR
kernel - 6:24 mins
functional - 13 mins
build - 14 mins

baseline
kernel - 7.24 mins
functional - 7.49 mins
build - 12 mins

omkar.podey’s picture

We can clearly see that functional tests are running slower , so i thought the only thing that functional tests are using that we changed is package_manager/tests/src/Traits/FixtureUtilityTrait.php but even without the changes in it they still seem to run slower.

retesting as i forgot to temporarily add it back to composer.json

omkar.podey’s picture

Performance test results

The old finder and the RecursiveDirectoryIterator are definitely looking at different files and the RecursiveDirectoryIterator is making more passes on the same files (indicated by the numbers in front of the discovered file names)

tedbow’s picture

Version: 8.x-2.x-dev » 3.0.x-dev
omkar.podey’s picture

From the latest test i think i can conclude 2 things

  1. There are no weird subdirectory inside subdirectory folders from a previous run or any old undeleted files
  2. The RecursiveDirectoryIterator is the only one that see's the weird structure.
omkar.podey’s picture

Issue summary: View changes
StatusFileSize
new243.52 KB

omkar.podey’s picture

Assigned: omkar.podey » Unassigned
omkar.podey’s picture

Assigned: Unassigned » wim leers

I did try to create a symlink locally but was unable to recreate the same pattern of paths.

wim leers’s picture

Assigned: wim leers » Unassigned

It doesn't need to be me who works on this, so if somebody wants to dig into this before I get to it, please do!

yash.rode’s picture

Assigned: Unassigned » yash.rode
yash.rode’s picture

When I debugged this I get ~7500 from withOutFinder() and ~250 files from withFinder().

yash.rode’s picture

StatusFileSize
new368.69 KB

Test failing with on self::assertSame($txt_files_finder, $txt_file_no_finder); line in \Drupal\Tests\automatic_updates\Unit\PerformanceTest::testPerformance(). The results are identical still the test is failing.

error

yash.rode’s picture

Assigned: yash.rode » wim leers
Status: Needs work » Needs review

Need help with the error

wim leers’s picture

Status: Needs review » Needs work

Reproduced the infamous recursive Omkar locally:

…
+    '/Users/wim.leers/core/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/sites/simpletest/36713557/ygxJCv2dGVN5BPYVCRvz/modules/example/.git/ignore.txt' => 1
+    '/Users/wim.leers/core/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/sites/simpletest/18800034/files/config_gKQI85yVN-z5lKiIDAsy5GJSfwwYfJeBfF3z64BkA6qwCgXD-pg4m_mECxzSHxcTvdV_RwLHCQ/sync/README.txt' => 1
+    '/Users/wim.leers/core/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/sites/simpletest/18067332/bP1bRMh0Cq4TfBGnoQUr/core/node_modules/ignore.txt' => 1
+    '/Users/wim.leers/core/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/omkar/sites/simpletest/18067332/bP1bRMh0Cq4TfBGnoQUr/sites/default/stage.txt' => 1
…

→ I'll continue here.

wim leers’s picture

Turns out that the reason the 3321933-performance-test is actually very simple:

  • withFinder() never calls \Symfony\Component\Finder\Finder::followLinks(), which is why it is ignoring symlinks entirely
  • withOutFinder() specifies $flags |= \FilesystemIterator::FOLLOW_SYMLINKS;, which causes it to do the opposite

⇒ the recursive Omkar mystery is solved: https://git.drupalcode.org/project/automatic_updates/-/merge_requests/64...

wim leers’s picture

That puts us at:

+    '/Users/wim.leers/core/omkar/core/.deprecation-ignore.txt' => 1
     '/Users/wim.leers/core/omkar/core/INSTALL.sqlite.txt' => 1
     '/Users/wim.leers/core/omkar/core/INSTALL.pgsql.txt' => 1
     '/Users/wim.leers/core/omkar/core/lib/Drupal/Core/README.txt' => 1
@@ @@
     '/Users/wim.leers/core/omkar/sites/simpletest/29115714/fXQhTRGlQ1k5Az4B6AYc/sites/example.com/files/ignore.txt' => 1
     '/Users/wim.leers/core/omkar/sites/simpletest/29115714/fXQhTRGlQ1k5Az4B6AYc/sites/simpletest/ignore.txt' => 1
     '/Users/wim.leers/core/omkar/sites/simpletest/29115714/fXQhTRGlQ1k5Az4B6AYc/private/ignore.txt' => 1
+    '/Users/wim.leers/core/omkar/sites/simpletest/29115714/fXQhTRGlQ1k5Az4B6AYc/.git/ignore.txt' => 1
     '/Users/wim.leers/core/omkar/sites/simpletest/29115714/fXQhTRGlQ1k5Az4B6AYc/modules/example/node_modules/ignore.txt' => 1
+    '/Users/wim.leers/core/omkar/sites/simpletest/29115714/fXQhTRGlQ1k5Az4B6AYc/modules/example/.git/ignore.txt' => 1
     '/Users/wim.leers/core/omkar/sites/simpletest/11853257/files/config_MnP16Yj8pfbM5wIkoIceZlpcxP5K1FivVn1ZzPJ9iY0ZkGih2Fc-hM2q

→ better, but still not perfect.

Reason for that: we're again not yet matching Finder's behavior, which has this:

    public function __construct()
    {
        $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
    }

… while we have nothing like that at all in our logic in PerformanceTest.

Now tests pass:

PHPUnit 9.5.26 by Sebastian Bergmann and contributors.

Testing /Users/wim.leers/core/modules/contrib/automatic_updates/tests/src/Unit
....................                                              20 / 20 (100%)

Time: 00:31.553, Memory: 10.00 MB

OK (20 tests, 40 assertions)
Process finished with exit code 0

So it's faster 100% of the time. Adding

var_dump(['with finder' => $with_finder, 'without finder' => $without_finder]);

makes that more clear:

Rarray(2) {
  ["with finder"]=>
  float(0.9062411785125732)
  ["without finder"]=>
  float(0.6564769744873047)
}
Rarray(2) {
  ["with finder"]=>
  float(0.9076869487762451)
  ["without finder"]=>
  float(0.6771509647369385)
}
Rarray(2) {
  ["with finder"]=>
  float(0.9273748397827148)
  ["without finder"]=>
  float(0.6514010429382324)
}
Rarray(2) {
  ["with finder"]=>
  float(0.9032847881317139)
  ["without finder"]=>
  float(0.6541171073913574)
}
<snip>

I bet that the root cause for the actual MR (https://git.drupalcode.org/project/automatic_updates/-/merge_requests/59...) was similar: none of the code in package_manager is calling Finder::followLinks()!

wim leers’s picture

New MR opened … waiting for it to fail before pushing the commit that will remove \FilesystemIterator::FOLLOW_SYMLINKS 🤓

wim leers’s picture

Looks like \FilesystemIterator::CURRENT_AS_SELF is causing phpstan confusion? 😳 😬 Trying more…

wim leers’s picture

Well well well … looks like this even works just like that, by just literally porting the MR I had created months ago against 8.x-2.x to 3.0.x (and fixing the PHPStan violations which even then were not running anymore apparently) … 😳

I bet:

But! It's still slower! The functional tests no longer time out like in January, but they run excruciatingly slow. So pushed a commit just now to stop following symlinks, just like the validators were doing previously.

wim leers’s picture

Hah! ec30ba81 even finished before d2c2aac2 even despite it starting tests roughly an hour later… 😅 In fact, it's currently still going:

…
00:51:49.032 Drupal\Tests\automatic_updates_extensions\Functional\PreUpda   3 passes                                      
01:21:38.843 Drupal\Tests\automatic_updates_extensions\Functional\UpdateE   3 passes                                      
01:21:40.820 Drupal\Tests\automatic_updates_extensions\Functional\Display   2 passes                                      
01:21:41.494 Drupal\Tests\automatic_updates\Functional\TableLooksCorrectT   2 passes    
…

wim leers’s picture

⚠️ While #49 matches HEAD's current behavior, after #3319507: Add symlink support to Composer Stager 2.0, require that version, and simplify UX & tests accordingly lands, we will be accepting most symlinks. Which means validators should also follow them while validating. Captured at #3319507-26: Add symlink support to Composer Stager 2.0, require that version, and simplify UX & tests accordingly.

CorePackageManifestTest and FixtureUtilityTrait will not be affected.

wim leers’s picture

Posted informative comments on the open MR, closed all other MRs. This is now hard-blocked on review, at last! 🥳

phenaproxima’s picture

This looks really good. I think it could maybe use a little consolidation and documentation but I have no objections otherwise.

wim leers’s picture

Assigned: Unassigned » wim leers
wim leers’s picture

#3305568: Create a validator that detects duplicate info.yml files in the stage on apply introduced DuplicateInfoFileValidator. It is what introduced ignoreUnreadableDirs() without documenting why. So that leaves some lack of clarity about how we should support this.

So I propose to instead just reuse core's ExtensionDiscovery 😊

phenaproxima’s picture

Status: Needs review » Needs work

A question, a request to revert something that didn't need to change, and a couple of nits. Then this, in my opinion, is ready and I will be glad to see it land!

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Reviewed & tested by the community

All feedback addressed! 👍 Some really good catches in there! 👏 Thanks for the thorough review 😊

phenaproxima’s picture

Title: Remove dependency on symfony/finder, document why we need symfony/config » Remove dependency on symfony/finder

Re-titling, because the final MR does not document why we need symfony/config. That's okay, though.

phenaproxima’s picture

Status: Reviewed & tested by the community » Fixed

Glad to see this alpha blocker bite the dust!

effulgentsia’s picture

Re-titling, because the final MR does not document why we need symfony/config.

Here's a follow-up for that: #3345039: Remove dependency on symfony/config

wim leers’s picture

#61: I did document that as part of working on this issue: see #11, and #3326246: Add symfony/config to core's dependencies for package_manager. This is all stated in the issue summary. Nowhere in Drupal core is it documented in code why a certain composer package is being depended upon, so doing that here would've been out of scope.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.