-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[EventDispatcher] Delay instantiation of a service until the respective listener is triggered in ContainerAwareEventDispatcher #12019
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
znerol
commented
Sep 24, 2014
Q | A |
---|---|
Bug fix? | no |
New feature? | no |
BC breaks? | no |
Deprecations? | no |
Tests pass? | yes |
Fixed tickets | #12007 |
License | MIT |
Doc PR | no |
I think I left the order of methods as they were before: master: modified: The patience algorithm yields a more readable patch: |
…not instantiate services in removeListener, if not necessary
Ok, fixed the coding style issues and also prevent |
if (null === $eventName) { | ||
foreach (array_keys($this->listenerIds) as $serviceEventName) { | ||
$this->lazyLoad($serviceEventName); | ||
$introspect = ($this->container instanceof IntrospectableContainerInterface); |
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.
parenthesis are not needed
I think this will break the integration with the TraceableDispatcher (used for the profiler panel). All lazy listeners will not be displayed as being a closure instead of identifying the real listener, making the events panel and the timeline integration useless for them (quite all listeners are lazy-loaded in Symfony) |
Well, it might be worth improving this in Symfony itself, but it must be done in a way which does not break the debugging capabilities |
From an architectural POV, do you think it would be legit to reassign the responsibility of providing information about the listeners to the actual event dispatcher? I.e. introduce a public method Another option would be to introduce a protected method |
Would it be possible to change the return value of |
@znerol adding Maybe we could have a special invokable proxy in the EventDispatcher component, which would be used by the ContainerAwareDispatcher (rather than using a closure), and could be known by the TraceableDispatcher to describe the inner callable instead |
So you are proposing something which is working like:
This looks like it could be useful outside of the event dispatcher component. Does something like this exist already? |
@znerol given that it needs to live either in the DI or EventDispatcher components to be usable here, and that it is not that useful in DI IMO (using proxy for services themselves is much more likely to be useful as the case where you inject a callable built with a method on a service is not that common), I would keep it in EventDispatcher, alongside the ContainerAwareDispatcher. and I would create an interface implemented by this class and supported by the TraceableDispatcher: interface IntrospectableCallable
{
/**
* @return callable
*/
public function getInnerCallable();
} |
return true; | ||
} | ||
$proxy = function ($event, $eventName, $dispatcher) use ($container, $serviceId, $method) { | ||
call_user_func(array($container->get($serviceId), $method), $event, $eventName, $dispatcher); |
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 not just use:
$container->get($serviceId)->$method($event, $eventName, $dispatcher);
Did some wall-time benchmarking (gist) and the results indicate that this version of the A repetition of the experiment in #12007 did result in the same performance improvement for the Drupal request event chain. I also manually verified that the events are shown properly in the symfony debug toolbar. |
@@ -268,6 +269,13 @@ private function getListenerInfo($listener, $eventName) | |||
$info = array( | |||
'event' => $eventName, | |||
); | |||
// Unpack lazy container service listener. | |||
if ($listener instanceof LazyServiceListener) { |
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 looks like a hack. What about implementing the IntrospectableCallable
as suggested by @stof 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.
The very reason for this patch is to prevent preliminary service instantiation. I'm not comfortable with adding a public getInnerCallable
which suffers exactly from this side-effect.
Then there is already a WrappedListener
class. I initially was tempted to extract a WrappedListenerInterface
from there but that would have made the code in TraceableEventDispatcher
less obvious. Mainly because in that case getWrappedListener
would have been used for two very different things. Introducing IntrospectableCallable
without touching the WrappedListener
also would result in a confusing coexistence of two similar concepts in the same 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.
@znerol your implementation also triggers the lazy-loading of the service when tracing events. But instead of providing an interface usable by other implementations of a lazy-loading dispatcher, it is tied to the Symfony container
/** | ||
* Returns the container. | ||
*/ | ||
public function getContainer() |
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.
IMO, you should not have these getters once you add the method returning the callable
This is obvious, the code is very explicit about that fact.
The It indeed seems odd that the |
@znerol the goal of making TraceableEventDispatcher depend on an interface rather than the LazyServiceListener is that could be possible to have a different lazy dispatcher using its own implementation of this interface and beign supported by the TraceableEventDispatcher directly, without having to edit it. Your current implementation would force replacing the TraceableEventDispatcher if you bring your own lazy-loading dispatcher (or getting useless tracing). And I'm not saying that the IntrospectableListener should be introspected for something else than debugging. But having the interface for it would be cleaner (and it would avoid having to make TraceableEventDispatcher aware of the way LazyServiceListener makes the listener lazy, which is the case currently). |
@stof I'd like to understand the real-world consequences of this issue and I'm now trying to find alternative event dispatcher implementations which may be affected by this change. If you know of any, please link them here. |
@znerol I don't know of any such implementation, but someone could do one in the future (at some point, there was a PR to make such a dispatcher implementation based on Pimple for Silex, but it has not been merged). Having a design allowing such extension in the component will be great (and it does not have any drawback compared to your current implementation supporting the LazyServiceListener in the TraceableEventListener, while making the code cleaner, so this would be a win) |
Ok, this is the situation: I pointed out the drawback of @stof suggestion and he rightfully criticizes the ugliness in To summarize: In my point of view introducing a public |
I don't see why introducing Moreover if we don't, we a patch in the |
I discovered another aspect about this change which should be taken into account when deciding on how to proceed. With the patch the |
@znerol The question is: is it significant? I mean, double time seems like a lot, but if we are talking about a very small number, it might be insignificant. |
Your own implementation of the TraceableDispatcher integration also suffers from this side-effect. My proposal does not introduce the side-effect. It makes the existing behavior of this PR cleaner (and more powerful for other integrations). |
@fabpot Depends on the number of subscribers. With a growing number even the time necessary to construct the service in the current implementation can grow rather big. I've benchmarked that and compared it to a method where all service ids would be simply injectied as an array into the constructor of the event dispatcher. The performance gain is impressive, however it suffers from the same problems outlined by @stof in #12069. Therefore it is likely that Drupal will use a custom implementation of the It is possible that the benchmark is flawed, it has not been confirmed by anybody yet. |
I close this, turns out that Drupal will very likely get its own version of the event dispatcher. There is no point in solving the lazy loading issue here if that degrades service construction performance even more. |
znerol, how is Drupal using its own dispatcher better than improving Symfony's? It sounds like fabpot is open to it. There's no need to close this PR, even if we did commit an alternate implementation to Drupal. |
@znerol Can you elaborate. I'm more than willing to make things faster for everyone. Instead of creating your own event dispatcher, is't there a way to make things better for everyone? I thought we were close to an acceptable solution with this PR. |
It looks like the main problem is the myriad of function calls executed upon instantiation of the event manager service. The benchmarks are over here: https://www.drupal.org/node/1972300#comment-9216069 @Nicofuma tried a similar optimization here #12069 but it turned out that this would violate the expectation that changed priorities should be reflected without the need of recompiling the container. |
This pull request here is even worse when it comes to instantiation performance and hence we'd need to try something different anyway. Therefore let's take the discussion to #12007 |