Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[SecurityBundle] Fixed a memory leak in SecurityBundle\Security\FirewallMap #22605

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

Closed
wants to merge 8 commits into from
Closed

[SecurityBundle] Fixed a memory leak in SecurityBundle\Security\FirewallMap #22605

wants to merge 8 commits into from

Conversation

udavka
Copy link

@udavka udavka commented May 1, 2017

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

There is a memory leak in Symfony\Bundle\SecurityBundle\Security\FirewallMap, which leads to not autodestroy Symfony\Component\HttpFoundation\Request object after the request has been proceeded. This object is quite big and uses many internal subobjects, and as a result this leads to an essential memory leak, which can affect long-lived applications like php-pm.
How to reproduce: create a default sample Symfony application, add a destructor for the Request class, add unset($request); at the end of web/app.php and make sure that this destructor is not called after unset().
With this fix the destructor is called immediately after unset(), and that means no memory leak for the Request object.

@@ -78,6 +78,8 @@ public function onKernelFinishRequest(FinishRequestEvent $event)
{
$request = $event->getRequest();

$this->map->detachListeners($event->getRequest());
Copy link
Member

Choose a reason for hiding this comment

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

This may not always work (think of other implementations of the FirewallMapInterface).

Copy link
Author

Choose a reason for hiding this comment

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

Fixed by replacing the implementation.

*
* @param Request $request
*/
public function detachListeners(Request $request);
Copy link
Member

Choose a reason for hiding this comment

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

Adding a new method to an interface is a BC break and not allowed by our BC promise.

Copy link
Author

Choose a reason for hiding this comment

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

Fixed by replacing the implementation.

@chalasr
Copy link
Member

chalasr commented May 2, 2017

But doesn't the issue concern only the SecurityBundle map due to its per-request cache? Wouldn't mapping contexts by Request object hash instead of Request instances be enough, changing the \SplObjectStorage to an array?

@nicolas-grekas nicolas-grekas added this to the 3.2 milestone May 2, 2017
@udavka
Copy link
Author

udavka commented May 2, 2017

@chalasr: The basic problem is the using of FirewallMapInterface to refer to long-lived services. There is no idea of destroying/detaching of FirewallMap from inside Firewall, because both of them are services and live forever.
Initially the FirewallMapInterface is intended only to get information and has nothing to initialize or to finalize itself. Even if we change the implementation of SecurityBundle\Security\FirewallMap to not have a Request reference, there will be a possibility to add a leak in the future.

@ogizanagi
Copy link
Contributor

ogizanagi commented May 12, 2017

@udavka : I don't get how it can improve anything when talking about the SecurityBundle FirewallMap. Unsetting the context for a given request won't free anything, as the services are still referenced by the container, right? The only leaking object is the request, indeed due to the \SplObjectStorage used for cache purpose. Using spl_object_hash($request) instead is the only thing required for solving the issue to me.

Even if we change the implementation of SecurityBundle\Security\FirewallMap to not have a Request reference, there will be a possibility to add a leak in the future.

Which leak?

Regarding the component FirewallMap, I don't get why you'd like to remove the listeners once a request is finished. What about subsequent requests? Are you destroying the listeners and recreating and adding them to the map for each request? If so, why? Aren't they stateless?
Also you don't need new interface methods/new interface nor tweaking the Firewall for this I guess: just register the FirewallMap as a listener of KernelEvents::FINISH_REQUEST in order to call detachListeners.

@udavka
Copy link
Author

udavka commented May 12, 2017

@ogizanagi Symfony is able to process many requests without restarting the whole framework. This idea is used in long-lived applications. Services are not leaks, because they are not recreated on every request. But Request is not a service and must be destroyed after the processing.
As you can see in the code, detaching of listeners is dependent on request, so no subsequent requests can be affected.
Using of spl_object_hash($request) is not an option, because the resulting hash can be reused later and anyway there will be a small leak in form of an array item [hash => listener].
I seems that the idea about KernelEvents::FINISH_REQUEST is the only one possible solution to make this object not leakable without BC breaks, but in this case the whole logic of the FirewallMap object will be inverted - it is initialized by another component, but it wants to clean up itself. This is actually the big hole for future problems.

@ogizanagi
Copy link
Contributor

ogizanagi commented May 12, 2017

Using of spl_object_hash($request) is not an option, because the resulting hash can be reused later.

Indeed, as the previous request is destroyed as this point. Good point.

small leak in form of an array item [hash => listener]

Sure, but not sure how much it can really affect long-living applications, as its one array item (per request though) just holding references.

I seems that the idea about KernelEvents::FINISH_REQUEST is the only one possible solution to make this object not leakable without BC breaks, but in this case the whole logic of the FirewallMap object will be inverted - it is initialized by another component, but it wants to clean up itself. This is actually the big hole for future problems.

Looks acceptable to me, and would allow to be merged as a bugfix in lower branches.
Otherwise, creating a new interface would allow doing it your way without BC break, but only as a new feature, so in 3.4.

private function detachListeners(Request $request)
{
unset($this->contexts[$request]);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO you should directly unset the request in the onKernelFinishRequest method, that would make one less method call...

Copy link
Author

Choose a reason for hiding this comment

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

fixed

@@ -75,4 +78,34 @@ private function getFirewallContext(Request $request)
}
}
}

/**
* @param FinishRequestEvent $event
Copy link
Contributor

Choose a reason for hiding this comment

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

To remove, as it does not bring anything more than the signature.

Copy link
Author

Choose a reason for hiding this comment

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

fixed

/**
* Get subscribed events.
*
* @return array Subscribed events
Copy link
Contributor

Choose a reason for hiding this comment

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

The whole docblock should be replaced by

/**
 * {@inheritdoc}
 */

Copy link
Author

Choose a reason for hiding this comment

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

fixed

@GromNaN
Copy link
Member

GromNaN commented May 13, 2017

Since the matching context relates to the request, this information should have been cached inside the request parameters.

This inside the getFirewallContext method.

// code in 3.2
    private function getFirewallContext(Request $request)
    {
        if ($this->contexts->contains($request)) {
            return $this->contexts[$request];
        }
        foreach ($this->map as $contextId => $requestMatcher) {
            if (null === $requestMatcher || $requestMatcher->matches($request)) {
                return $this->contexts[$request] = $this->container->get($contextId);
            }
        }
    }

We can remove the $contexts object map and store information inside the request attributes.

// proposal
    private function getFirewallContext(Request $request)
    {
        if ($request->attributes->has('_security_firewall_context')) {
            return $this->container->get($request->attributes->get('_security_firewall_context'));
        }
        foreach ($this->map as $contextId => $requestMatcher) {
            if (null === $requestMatcher || $requestMatcher->matches($request)) {
                $request->attributes->set('_security_firewall_context', $contextId);
                return $this->container->get($contextId);
            }
        }
    }

@ogizanagi
Copy link
Contributor

ogizanagi commented May 14, 2017

Indeed, @GromNaN's suggestion looks even better to me :)

@nicolas-grekas
Copy link
Member

Status: needs work

@fabpot
Copy link
Member

fabpot commented Jun 9, 2017

Closing in favor of #22943

@fabpot fabpot closed this Jun 9, 2017
fabpot added a commit that referenced this pull request Jun 14, 2017
…he request parameters (GromNaN)

This PR was squashed before being merged into the 3.2 branch (closes #22943).

Discussion
----------

[SecurityBundle] Move cache of the firewall context into the request parameters

Following [this proposal](#22605 (comment)). Since the matching context relates to the request, this information should have been cached inside the request parameters.

| Q             | A
| ------------- | ---
| Branch?       | 3.2
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #22605
| License       | MIT
| Doc PR        | n/a

* Avoid memory leak when handling multiple requests
* Adding the new request parameter `_firewall_context` might be considered as a breaking change. That adds a new "public" property that could be used by end developers.

Commits
-------

b3203cb [SecurityBundle] Move cache of the firewall context into the request parameters
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.

9 participants