-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
[DI] Add ServiceLocatorArgument to generate array-based locators optimized for OPcache shared memory #27783
Conversation
d7ae279
to
a5b4efb
Compare
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.
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" /> |
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.
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( |
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.
Here is an example of an optimized service locator: we now dump a static array instead of an array of closures.
a5b4efb
to
934299f
Compare
} | ||
|
||
/** | ||
* @return array The values to lazily iterate over |
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.
description doesn't seem relevant anymore, @return Reference[]
?
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.
fixed thanks!
} | ||
|
||
/** | ||
* @param Reference[] $values The service references to lazily iterate over |
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.
same
bc38cdd
to
b274613
Compare
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\DependencyInjection\Argument; |
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.
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?
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.
like RewindableIterator: this is internal class, I don't think it should be at the root of the component
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.
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.
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.
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...
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.
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; |
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 means we analyze only once. So does it actually make sense to have it in processValue
?
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.
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); |
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.
why would $load
contain an error in some cases ? This looks like a confusing API
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'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) |
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 it actually make sense to have it in the parent class ?
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.
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}) ? '_' : '_'}"; |
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 is this about actually ?
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.
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); |
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.
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)); |
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 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 :))
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.
indeed, a service locator accepts maps, not sequences.
0ae2098
to
9e7d579
Compare
…mized for OPcache shared memory
9e7d579
to
6c8e957
Compare
Comments addressed, thanks for the review. |
…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
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 byServiceArgumentResolver
, which can grow fast (it has as many entries as there are actions.)