Skip to content

[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

Merged
merged 1 commit into from
Sep 14, 2017

Conversation

derrabus
Copy link
Member

@derrabus derrabus commented Sep 11, 2017

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.

class ResettableServicePass implements CompilerPassInterface
{
const RESET_TAG = 'kernel.reset';
const METHOD_ATTRIBUTE = 'method';
Copy link
Member

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

Copy link
Member Author

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));
Copy link
Member

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

Copy link
Member

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

sweet :)

* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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
Copy link
Member

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';
Copy link
Member

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(
Copy link
Member

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;
Copy link
Member

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
Copy link
Member

Choose a reason for hiding this comment

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

no needed

Copy link
Member Author

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()
Copy link
Member

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));
Copy link
Member

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());
Copy link
Member

@nicolas-grekas nicolas-grekas Sep 11, 2017

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];
Copy link
Member

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?

Copy link
Member Author

@derrabus derrabus Sep 12, 2017

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.

Copy link
Member

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.

@nicolas-grekas nicolas-grekas added this to the 3.4 milestone Sep 11, 2017
@nicolas-grekas
Copy link
Member

About the todo:

Add an interface, make the "method" attribute optional and enable autoconfiguration.

I suggest doing so in another PR

Add a config option to enable/disable this feature.

I suggest not adding this at all

Configure leaking services of the core bundles as resettable.

in next PRs to come yes!

@nicolas-grekas nicolas-grekas changed the title [FrameworkBundle][WIP] A DI tag for resettable services [FrameworkBundle] A DI tag for resettable services Sep 12, 2017
@nicolas-grekas nicolas-grekas changed the title [FrameworkBundle] A DI tag for resettable services [FrameworkBundle] Add DI tag for resettable services Sep 12, 2017
@derrabus
Copy link
Member Author

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.

@derrabus derrabus force-pushed the 3.4-resettable-services branch 2 times, most recently from ac7b2c6 to 9d2d4e8 Compare September 12, 2017 09:30
*/
class ResettableServicePass implements CompilerPassInterface
{
const RESET_TAG = 'kernel.reset';
Copy link
Member

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);
Copy link
Member

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) {
Copy link
Member

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(
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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();
Copy link
Member

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?

Copy link
Member Author

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.

@derrabus derrabus force-pushed the 3.4-resettable-services branch from 9d2d4e8 to f39ac55 Compare September 12, 2017 09:42
@@ -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);
Copy link
Member

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)

Copy link
Member Author

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.

Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@derrabus derrabus force-pushed the 3.4-resettable-services branch 4 times, most recently from 88b0983 to ea77c45 Compare September 12, 2017 10:14
Copy link
Member

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

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);
Copy link
Member

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)
Copy link
Member

@nicolas-grekas nicolas-grekas Sep 12, 2017

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.

Copy link
Member Author

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;

Copy link
Member

Choose a reason for hiding this comment

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

extra line

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed.

@derrabus
Copy link
Member Author

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.

@nicolas-grekas nicolas-grekas changed the title [FrameworkBundle] Add DI tag for resettable services [FrameworkBundle][HttpKernel] Add DI tag for resettable services Sep 12, 2017
@stof
Copy link
Member

stof commented Sep 12, 2017

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;
Copy link
Member

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)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Sep 12, 2017

I'm personally not sure about another config option, because config options add complexity for users.
Here, this hook point is really nice for eg cache pools that could then clear their deferred item lists.
This happens at destruct time right now, but this is kinda a hack, destruct time to do biz logic is not the best.
We could instead document that the logic has to be as fast as possible (like emptying an array most of the time).

If this needs a longer discussion, I suggest doing it after this PR has been merged?

@derrabus derrabus force-pushed the 3.4-resettable-services branch from ea77c45 to 4d05869 Compare September 12, 2017 12:40
@derrabus
Copy link
Member Author

If this needs a longer discussion, I suggest doing it after this PR has been merged?

I agree.

Copy link
Contributor

@ogizanagi ogizanagi left a 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));
Copy link
Contributor

@ogizanagi ogizanagi Sep 14, 2017

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

Copy link
Member Author

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
Copy link
Contributor

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

All right. Changed.

@fabpot
Copy link
Member

fabpot commented Sep 14, 2017

@derrabus Can you make the 2 little changes asked by @ogizanagi so that I can merge this one? Thanks.

@derrabus derrabus force-pushed the 3.4-resettable-services branch from 6d3f424 to 8499bca Compare September 14, 2017 20:25
@derrabus
Copy link
Member Author

@fabpot Done.


class ServiceResetListenerTest extends TestCase
{
public function setUp()
Copy link
Member

Choose a reason for hiding this comment

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

should be protected

Copy link
Member Author

Choose a reason for hiding this comment

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

@derrabus derrabus force-pushed the 3.4-resettable-services branch from 8499bca to d9a6b76 Compare September 14, 2017 20:29
@fabpot
Copy link
Member

fabpot commented Sep 14, 2017

Thank you @derrabus.

@fabpot fabpot merged commit d9a6b76 into symfony:3.4 Sep 14, 2017
fabpot added a commit that referenced this pull request Sep 14, 2017
…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.
@derrabus derrabus deleted the 3.4-resettable-services branch September 14, 2017 20:50
nicolas-grekas added a commit that referenced this pull request Sep 15, 2017
…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.
chalasr pushed a commit that referenced this pull request Sep 24, 2017
…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.
nicolas-grekas added a commit that referenced this pull request Sep 25, 2017
… 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
fabpot added a commit that referenced this pull request Oct 5, 2017
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 was referenced Oct 18, 2017
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.

8 participants