-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FrameworkBundle][HttpKernel] Add DI tag for resettable services #24155
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
9847c51
to
2aad68b
Compare
class ResettableServicePass implements CompilerPassInterface | ||
{ | ||
const RESET_TAG = 'kernel.reset'; | ||
const METHOD_ATTRIBUTE = 'method'; |
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.
We don't add constants for them in Symfony
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.
Tag name is now a parameter, attribute name has been inlined.
{ | ||
foreach ($this->services as $id => $service) { | ||
foreach ($this->resetMethods[$id] as $method) { | ||
call_user_func(array($service, $method)); |
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.
$service->$method()
would be faster by avoid the call_user_func
frame in the stack
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.
sweet :)
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; |
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.
I propose to move the pass and the event subscriber in HttpKernel, to make it easier to reuse.
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.
Good idea. In this case, tag name and method attribute should be constructor parameters of the pass, I guess.
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.
for the tag name yes, that's what we do usually,
for the attribute name, no need IMHO
/** | ||
* @author Alexander M. Turek <me@derrabus.de> | ||
* | ||
* @internal |
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 need for this
class ResettableServicePass implements CompilerPassInterface | ||
{ | ||
const RESET_TAG = 'kernel.reset'; | ||
const METHOD_ATTRIBUTE = 'method'; |
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 two const should be removed (for consistency at least: we don't use consts for tags in any pass yet)
|
||
foreach ($tags as $attributes) { | ||
if (!isset($attributes[self::METHOD_ATTRIBUTE])) { | ||
throw new \RuntimeException( |
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.
should be on one line
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Bundle\FrameworkBundle\EventListener; |
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.
HttpKernel namespace
* | ||
* @author Alexander M. Turek <me@derrabus.de> | ||
* | ||
* @internal |
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 needed
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.
Removed.
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function getSubscribedEvents() |
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.
should be moved last, after onKernelTerminate
{ | ||
foreach ($this->services as $id => $service) { | ||
foreach ($this->resetMethods[$id] as $method) { | ||
call_user_func(array($service, $method)); |
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.
or just $service->$method();
@@ -117,6 +118,7 @@ public function build(ContainerBuilder $container) | |||
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); | |||
$this->addCompilerPassIfExists($container, FormPass::class); | |||
$container->addCompilerPass(new WorkflowGuardListenerPass()); | |||
$container->addCompilerPass(new ResettableServicePass()); |
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.
as PassConfig::TYPE_AFTER_REMOVING
?
); | ||
} | ||
|
||
$methods[$id][] = $attributes[self::METHOD_ATTRIBUTE]; |
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.
Do we really need to allow several methods? I'm not sure I see a use case where a class would require two calls to be resetted, do you?
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.
I don't, but I did not see a reason to forbid it. Also, I would need to limit the kernel.reset
tag to max one tag per service then.
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 such cases, we usually just ignore the next tags and always take index 0
.
We should do the same here IMHO.
About the todo:
I suggest doing so in another PR
I suggest not adding this at all
in next PRs to come yes! |
About the config option: This feature should support PHPPM and other scenarios where we reuse the kernel after one request. I would consider those scenarios as rather exotic. The default case for php application is still a shared-nothing architecture and if I cannot disable this feature there, Symfony would consume CPU time to cleanup a container just to throw it away immediately afterwards. |
ac7b2c6
to
9d2d4e8
Compare
*/ | ||
class ResettableServicePass implements CompilerPassInterface | ||
{ | ||
const RESET_TAG = 'kernel.reset'; |
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.
should be removed, we don't use consts for tags
@@ -117,6 +118,7 @@ public function build(ContainerBuilder $container) | |||
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); | |||
$this->addCompilerPassIfExists($container, FormPass::class); | |||
$container->addCompilerPass(new WorkflowGuardListenerPass()); | |||
$container->addCompilerPass(new ResettableServicePass('kernel.reset'), PassConfig::TYPE_AFTER_REMOVING); |
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.
'kernel.reset' should be the default value of the constructor arg instead (as done in other passes)
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE); | ||
$methods[$id] = array(); | ||
|
||
foreach ($tags as $attributes) { |
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.
should be removed, no need for multiple methods, we should use only the one in $tags[0]['method']
} | ||
|
||
$container->getDefinition(ServiceResetListener::class) | ||
->setArguments(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.
to allow for some flexibility, this should use replaceArgument (the xml declaration will need empty placeholders for pre-declaring those: <argument />
+ <argument type="collection" />
)
class ServiceResetListener implements EventSubscriberInterface | ||
{ | ||
/** | ||
* @var \Traversable |
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.
redundant with constructor declaration, should be removed
private $services; | ||
|
||
/** | ||
* @var 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.
same
*/ | ||
public function process(ContainerBuilder $container) | ||
{ | ||
$services = $methods = 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.
if these are empty after processing, the listener should be removed, isn't it?
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.
That's probably a good idea.
9d2d4e8
to
f39ac55
Compare
@@ -117,6 +118,7 @@ public function build(ContainerBuilder $container) | |||
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); | |||
$this->addCompilerPassIfExists($container, FormPass::class); | |||
$container->addCompilerPass(new WorkflowGuardListenerPass()); | |||
$container->addCompilerPass(new ResettableServicePass('kernel.reset'), PassConfig::TYPE_AFTER_REMOVING); |
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.
doing this after removing is a bad idea, as some resettable services may get inlined (due to being referenced only once before this pass adds a second reference). It should stay before optimizations are performed.
the general rule is that anything adding references should never run after optimizations (as optimizations are applied based on references). Removing references is fine (it may make us miss some optimization opportunities, but it cannot break the container)
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 was a suggestion by @nicolas-grekas. I see your point and would revert this line, if it's okay with @nicolas-grekas.
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.
I'm OK yes, let's revert
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.
Done.
88b0983
to
ea77c45
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.
last comments :)
@@ -117,6 +118,7 @@ public function build(ContainerBuilder $container) | |||
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); | |||
$this->addCompilerPassIfExists($container, FormPass::class); | |||
$container->addCompilerPass(new WorkflowGuardListenerPass()); | |||
$container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_AFTER_REMOVING); |
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.
so, as discussed: let's remove TYPE_AFTER_REMOVING, my bad
$methods[$id] = $attributes['method']; | ||
} | ||
|
||
$container->getDefinition(ServiceResetListener::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.
The method should start with if (!$container->has(ServiceResetListener::class)) { return; }
, and here we should use "findDefinition" instead of "getDefinition", but "removeDefinition" when $services is empty.
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.
Done.
class ServiceResetListener implements EventSubscriberInterface | ||
{ | ||
private $services; | ||
|
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.
extra line
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.
Removed.
I hope, I've caught every comment. The PR looks mergable to me now. I've left my todo list in the PR description so we know the steps for follow-up PRs. |
The config option looks sensible to me, as we should indeed avoid resetting services when the process will die anyway. |
|
||
foreach ($container->findTaggedServiceIds($this->tagName, true) as $id => $tags) { | ||
$services[$id] = new Reference($id, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE); | ||
list($attributes) = $tags; |
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.
I think $attributes = $tags[0]
would be more readable (and it is strictly equivalent, as list($attributes)
expects the id to be 0, not anything)
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.
Done.
I'm personally not sure about another config option, because config options add complexity for users. If this needs a longer discussion, I suggest doing it after this PR has been merged? |
ea77c45
to
4d05869
Compare
I agree. |
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.
👍
$attributes = $tags[0]; | ||
|
||
if (!isset($attributes['method'])) { | ||
throw new RuntimeException(sprintf('Tag %s requires the attribute method to be set.', $this->tagName)); |
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.
Perhaps
- Tag %s requires the attribute method to be set.
+ Tag %s requires the "method" attribute to be set.
for clarity sake (and consistency with the similar exception messages in the core).
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.
Yes, that's better. Changed.
class ServiceResetListenerTest extends TestCase | ||
{ | ||
/** | ||
* @before |
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.
We never used this annotation in Symfony core (AFAIK). Shouldn't we use setUp
as usual?
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.
All right. Changed.
@derrabus Can you make the 2 little changes asked by @ogizanagi so that I can merge this one? Thanks. |
6d3f424
to
8499bca
Compare
@fabpot Done. |
|
||
class ServiceResetListenerTest extends TestCase | ||
{ | ||
public function setUp() |
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.
should be protected
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.
✅
8499bca
to
d9a6b76
Compare
Thank you @derrabus. |
…e services (derrabus) This PR was merged into the 3.4 branch. Discussion ---------- [FrameworkBundle][HttpKernel] Add DI tag for resettable services | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23984 | License | MIT | Doc PR | TODO This PR uses #24033 to introduce a DI tag for resettable services. TODO after merge: * Add an interface, make the "method" attribute optional and enable autoconfiguration. * Consider adding a config option to enable/disable this feature. * Configure leaking services of the core bundles as resettable. Commits ------- d9a6b76 A DI tag for resettable services.
…rrabus) This PR was merged into the 3.4 branch. Discussion ---------- [FrameworkBundle] Reset stopwatch between requests | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23984 | License | MIT | Doc PR | N/A Follow-up to #24155. This PR ensures that the stopwatch is reset between requests. Commits ------- 7c3bdd9 Reset stopwatch.
…n requests (derrabus) This PR was merged into the 3.4 branch. Discussion ---------- [SecurityBundle] Reset the authentication token between requests | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23984 | License | MIT | Doc PR | N/A Follow-up to #24155. This PR resets the token storage, making sure there's no user logged in at the beginning of a new request. Commits ------- e46b366 Reset the authentication token between requests.
… pool's local state (nicolas-grekas) This PR was merged into the 3.4 branch. Discussion ---------- [Cache] Add ResettableInterface to allow resetting any pool's local state | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - To allow pools to leverage #24155 so that they can be used in multi-request loops. Commits ------- 14c91f2 [Cache] Add ResettableInterface to allow resetting any pool's local state
This PR was merged into the 3.4 branch. Discussion ---------- [FrameworkBundle][HttpKernel] Reset profiler | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | yes | Fixed tickets | #18244 | License | MIT | Doc PR | N/A This PR adds the ability to reset the profiler between requests. Furthermore, the profiler service has been tagged with the new `kernel.reset` tag from #24155. For this, I had to readd the ability to define multiple reset methods for a service. Note: This PR requires twigphp/Twig#2560. Commits ------- 8c39bf7 Reset profiler.
This PR uses #24033 to introduce a DI tag for resettable services.
TODO after merge: