Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[DI] Add ServiceLocatorArgument to generate array-based locators optimized for OPcache shared memory #27783

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
Jul 7, 2018

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Jun 30, 2018

Q A
Branch? master
Bug fix? no
New feature? yes
BC breaks? -
Deprecations? no
Tests pass? yes
Fixed tickets -
License MIT
Doc PR -

Right now, to generate service locators, we use collections of closures described using ServiceClosureArgument. This works well, but it doesn't scale well when the number of services grows, because we have to load as many closures as there are services, even if we never call them.

This PR introduces ServiceLocatorArgument, which describes the same thing, but allows dumping optimized locators: instead of a collection of closures, this generates a static array that OPcache can put in shared memory (see fixtures for example.)

Once this PR is merged, we'll be able to update ServiceLocatorPass::register() to leverage it and generate these optimized locators everywhere. One particular I have in mind in the locator used by ServiceArgumentResolver, which can grow fast (it has as many entries as there are actions.)

Copy link
Member Author

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

PR is now fully fledged and ready. Service locators defined as collections of closures are dumped as optimized locators and locators defined using the new ServiceLocatorArgument are still deduplicated as the previous.

</service>
<argument type="service_locator">
<argument key="session" type="service" id="session" on-invalid="ignore" />
<argument key="initialized_session" type="service" id="session" on-invalid="ignore_uninitialized" />
Copy link
Member Author

Choose a reason for hiding this comment

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

Here is an example showing easier definition of service locators.

}, 'baz' => function (): ?\Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition {
return ($this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] ?? $this->privates['Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition());
})))->withContext('foo_service', $this));
return $this->services['foo_service'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber((new \Symfony\Component\DependencyInjection\Argument\ServiceLocator(\Closure::fromCallable(array($this, 'getService')), array(
Copy link
Member Author

Choose a reason for hiding this comment

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

Here is an example of an optimized service locator: we now dump a static array instead of an array of closures.

}

/**
* @return array The values to lazily iterate over
Copy link
Member

Choose a reason for hiding this comment

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

description doesn't seem relevant anymore, @return Reference[]?

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed thanks!

}

/**
* @param Reference[] $values The service references to lazily iterate over
Copy link
Member

Choose a reason for hiding this comment

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

same

@nicolas-grekas nicolas-grekas force-pushed the di-closure-locator branch 2 times, most recently from bc38cdd to b274613 Compare July 1, 2018 19:40
* file that was distributed with this source code.
*/

namespace Symfony\Component\DependencyInjection\Argument;
Copy link
Contributor

Choose a reason for hiding this comment

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

Just asking, should it really be in this namespace? Seems a bit confusing to me. Can't we just keep it in Symfony\Component\DependencyInjection and find an appropriate name?

Copy link
Member Author

Choose a reason for hiding this comment

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

like RewindableIterator: this is internal class, I don't think it should be at the root of the component

Copy link
Contributor

@ogizanagi ogizanagi Jul 3, 2018

Choose a reason for hiding this comment

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

Fair enough. But this isn't an argument in the sense of the ArgumentInterface, i.e a representation used before resolution/compilation. This is the actual resolved item used at runtime, indeed like the RewindableGenerator. As both are internal, we could move them to a Resolved/Runtime sub-namespace, just in order to not bring confusion with actual ArgumentInterface implementations which are "descriptors". Those ones can be used by anyone.

Copy link
Member Author

@nicolas-grekas nicolas-grekas Jul 4, 2018

Choose a reason for hiding this comment

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

Still not convinced this deserves more. Not all classes in a sub-namespace have to implement the same interface. Here, if there is a rule, it's that all classes that have the Argument suffix should implement ArgumentInterface.
But I wouldn't bug on some others like this one...

Copy link
Contributor

Choose a reason for hiding this comment

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

No problem, just random thoughts.

@@ -33,6 +40,10 @@ protected function processValue($value, $isRoot = false)
}

if ($isRoot && !$value->isPublic()) {
if ($this->analyzingPass) {
$this->analyzingPass->process($this->container);
$this->analyzingPass = null;
Copy link
Member

Choose a reason for hiding this comment

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

this means we analyze only once. So does it actually make sense to have it in processValue ?

Copy link
Member Author

Choose a reason for hiding this comment

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

reverted actually, that's another topic for another PR

continue;
}
$definition = $this->container->findDefinition($id = (string) $v);
$load = !($e = $definition->getErrors()) ? $this->asFiles && !$this->isHotPath($definition) : reset($e);
Copy link
Member

Choose a reason for hiding this comment

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

why would $load contain an error in some cases ? This looks like a confusing API

Copy link
Member Author

Choose a reason for hiding this comment

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

It's not an API but an internal data structure, I'd prefer making it compact.

@@ -401,6 +402,35 @@ protected function getEnv($name)
}
}

protected function throw($message)
Copy link
Member

Choose a reason for hiding this comment

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

does it actually make sense to have it in the parent class ?

Copy link
Member Author

Choose a reason for hiding this comment

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

reverted that part :)

@@ -1645,7 +1617,7 @@ private function dumpValue($value, bool $interpolate = true): string
private function dumpLiteralClass(string $class): string
{
if (false !== strpos($class, '$')) {
return sprintf('${($_ = %s) && false ?: "_"}', $class);
return "\${(\$_ = {$class}) ? '_' : '_'}";
Copy link
Member

Choose a reason for hiding this comment

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

what is this about actually ?

Copy link
Member Author

Choose a reason for hiding this comment

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

the exact same written in a nicer way, but unrelated, now reverted

case 'iterator':
$arg = $this->getArgumentsAsPhp($arg, $name, $file, false);
try {
$arguments[$key] = new IteratorArgument($arg);
$arguments[$key] = 'iterator' === $type ? new IteratorArgument($arg) : new ServiceLocatorArgument($arg);
Copy link
Member

Choose a reason for hiding this comment

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

given that all the parsing of referenced is already factored inside getArgumentsAsPhp, I don't think it makes sense to group the 2 cases together and then performed if checks in all other statements

if (!is_array($argument)) {
throw new InvalidArgumentException(sprintf('"!iterator" tag only accepts sequences in "%s".', $file));
throw new InvalidArgumentException(sprintf('"!%s" tag only accepts sequences in "%s".', $value->getTag(), $file));
Copy link
Member

Choose a reason for hiding this comment

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

In xml each service is keyed, can't they be in yaml? If yes, service_locator also accepts mappings (maybe only mappings, not sequences?). c.f. #21553 (review) (glad to see the argument back btw :))

Copy link
Member

Choose a reason for hiding this comment

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

indeed, a service locator accepts maps, not sequences.

@nicolas-grekas nicolas-grekas force-pushed the di-closure-locator branch 2 times, most recently from 0ae2098 to 9e7d579 Compare July 4, 2018 13:57
@nicolas-grekas
Copy link
Member Author

Comments addressed, thanks for the review.

@nicolas-grekas nicolas-grekas merged commit 6c8e957 into symfony:master Jul 7, 2018
nicolas-grekas added a commit that referenced this pull request Jul 7, 2018
…d locators optimized for OPcache shared memory (nicolas-grekas)

This PR was merged into the 4.2-dev branch.

Discussion
----------

[DI] Add ServiceLocatorArgument to generate array-based locators optimized for OPcache shared memory

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | -
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Right now, to generate service locators, we use collections of closures described using `ServiceClosureArgument`. This works well, but it doesn't scale well when the number of services grows, because we have to load as many closures as there are services, even if we never call them.

This PR introduces `ServiceLocatorArgument`, which describes the same thing, but allows dumping optimized locators: instead of a collection of closures, this generates a static array that OPcache can put in shared memory (see fixtures for example.)

Once this PR is merged, we'll be able to update `ServiceLocatorPass::register()` to leverage it and generate these optimized locators everywhere. One particular I have in mind in the locator used by `ServiceArgumentResolver`, which can grow fast (it has as many entries as there are actions.)

Commits
-------

6c8e957 [DI] Add ServiceLocatorArgument to generate array-based locators optimized for OPcache shared memory
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.

5 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.