-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[DependencyInjection] Self-referenced 'service_container' service breaks garbage collection #11422
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
FWIW, $code .= <<<EOF
…
\$this->set('service_container', \$this); I'm not sure whether it is safe to also remove that call from the dumped constructor. It is a no-op for |
Travis test failure (only on 5.5) is not related to this PR. The second to last Travis build succeeded. |
Hm, I thought this would be a straightforward improvement/quick fix… 😉 Curious, did someone look at this already? |
@@ -261,6 +269,9 @@ public function has($id) | |||
*/ | |||
public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) | |||
{ | |||
if ($id === 'service_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.
Unfortunately, the service id is case insensitive (see code after your changes.) I don't think people are using anything else, but that's a slight BC break... which we can probably ignore.
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.
Any performance impact?
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.
Yeah, I wasn't sure whether it's worth to move the condition into the loop. For maximum BC, I guess we should…
Comparing a string variable with a fixed static string is hardly measurable, in the realm of microseconds.
(btw, would be great to remove all of the strtolower() futzing in an upcoming release; the total cost of that can be up to 100ms…)
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.
Moved the condition into the loop now.
Looks good to me. |
This could be merged in 2.3. |
@@ -197,6 +195,12 @@ public function set($id, $service, $scope = self::SCOPE_CONTAINER) | |||
|
|||
$id = strtolower($id); | |||
|
|||
if ($id === 'service_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.
Should be 'service_container' === $id
(and below too), shouldn'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.
Sorry, wasn't aware that Symfony enforces yoda conditions everywhere; fixed.
…llection (and clone).
👍 |
Thank you @sun. |
…service breaks garbage collection (sun) This PR was submitted for the master branch but it was merged into the 2.3 branch instead (closes #11422). Discussion ---------- [DependencyInjection] Self-referenced 'service_container' service breaks garbage collection | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Needs merge to | 2.4 | Fixed tickets | — | License | MIT | Doc PR | — #### Problem 1. `Container::__construct()` sets the `service_container` service ID to a self-reference of `$this`. 1. This breaks PHP garbage collection (as well as `clone`). #### Background * As explained in [Reference Counting Basics](http://php.net/manual/en/features.gc.refcounting-basics.php), PHP is not able to destruct and garbage collect variables that contain a reference to itself, because the `refcount` is always > 0. #### Example Use-Case A precompiled container that is cloned for multiple tests (because `::compile()` can be slow). ```php $precompiled = new ContainerBuilder(); // ... $precompiled->compile(); foreach ($precompiled->getServiceIds() as $id) { if ($precompiled->initialized($id)) { $precompiled->set($id, null); } } $precompiled_clean = clone $precompiled; $precompiled = NULL; // ... foreach ($tests as $test) { $container = clone $precompiled_clean; // Required without this PR: $container->set('service_container', $container); // ... } ``` * **Actual result [master]:** All references to the original `$precompiled` container are not destructed and cleaned up, because `$precompiled` still references itself, which can cause terribly hard to debug defects due to stale reference pointers in memory (especially with `ContainerAware` services, or things like e.g. PDO connections), as well as high memory consumption, etc. * **Expected result [PR]:** No references to `$precompiled` are left; everything is shut down, cleaned up, and garbage-collected upon `$precompiled = NULL;` #### Proposed solution 1. Hard-code a special behavior for the service ID `service_container`. 1. Remove the self-reference. 1. For now (until next major release), ensure that all `Container` methods work identically to before — instead of throwing exceptions when e.g. trying to set `service_container` to something that isn't `$this`… (which is technically possible right now, but the operation doesn't remotely make sense) #### API changes Per (3) above, this PR is backwards-compatible. However, for the sake of full disclosure, users may experience the following super-micro changes compared to HEAD: 1. The following method no longer exists in a dumped Container, because it is no longer a registered service: ```php protected function getServiceContainerService() { throw new RuntimeException('You have requested a synthetic service ("service_container"). The DIC does not know how to construct this service.'); } ``` But given the exception, you weren't able to call it anyway. 1. The order/position of `service_container` in the array returned by `Container::getServiceIds()` may be different. But that array has no particular order to begin with. (Only the component's own unit tests are expecting an identical result at times, which is why I added the hard-coded ID via `$id[]` last, instead of initializing it first with `$id` already.) 1. `$container->set('service_container', $not_the_container);` no longer works. Commits ------- 440322e Fixed self-reference in 'service_container' service breaks garbage collection (and clone).
Is there a PR already to fix the code marked as |
@hacfi there's none yet. |
… to allow garbage collection Follow up of symfony#11422
This issue has been fixed in symfony#11422. Due to a bad merge in 3bed1b7 it partially appeared again in Symfony 2.6 or higher.
This PR was merged into the 2.6 branch. Discussion ---------- [DependencyInjection] resolve circular reference | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #11422, #14445 | License | MIT | Doc PR | This issue has been fixed in #11422. Due to a bad merge in 3bed1b7 it partially appeared again in Symfony 2.6 or higher. Commits ------- 8b3b3ce [DependencyInjection] resolve circular reference
This PR was merged into the 2.3 branch. Discussion ---------- [DependencyInjection] Fixed missing tests | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Follow up of #11422 and #14446 Commits ------- 2892902 Fixed tests
I am getting
Is there a way to reference service container differently, so it does not break, or it is a bug introduced in this ticket? |
@githoober Can you give the stack trace of the exception ? |
And please open a dedicated issue for that instead of commenting on a closed PR |
Problem
Container::__construct()
sets theservice_container
service ID to a self-reference of$this
.clone
).Background
refcount
is always > 0.Example Use-Case
A precompiled container that is cloned for multiple tests (because
::compile()
can be slow).All references to the original
$precompiled
container are not destructed and cleaned up, because$precompiled
still references itself, which can cause terribly hard to debug defects due to stale reference pointers in memory (especially withContainerAware
services, or things like e.g. PDO connections), as well as high memory consumption, etc.No references to
$precompiled
are left; everything is shut down, cleaned up, and garbage-collected upon$precompiled = NULL;
Proposed solution
service_container
.Container
methods work identically to before — instead of throwing exceptions when e.g. trying to setservice_container
to something that isn't$this
… (which is technically possible right now, but the operation doesn't remotely make sense)API changes
Per (3) above, this PR is backwards-compatible. However, for the sake of full disclosure, users may experience the following super-micro changes compared to HEAD:
The following method no longer exists in a dumped Container, because it is no longer a registered service:
But given the exception, you weren't able to call it anyway.
The order/position of
service_container
in the array returned byContainer::getServiceIds()
may be different.But that array has no particular order to begin with.
(Only the component's own unit tests are expecting an identical result at times, which is why I added the hard-coded ID via
$id[]
last, instead of initializing it first with$id
already.)$container->set('service_container', $not_the_container);
no longer works.