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

Skip to content

Commit cd005e6

Browse files
committed
bug #11422 [DependencyInjection] Self-referenced 'service_container' 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).
2 parents 497c875 + 440322e commit cd005e6

File tree

1 file changed

+20
-2
lines changed

1 file changed

+20
-2
lines changed

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ public function __construct(ParameterBagInterface $parameterBag = null)
9393
$this->scopeChildren = array();
9494
$this->scopedServices = array();
9595
$this->scopeStacks = array();
96-
97-
$this->set('service_container', $this);
9896
}
9997

10098
/**
@@ -204,6 +202,12 @@ public function set($id, $service, $scope = self::SCOPE_CONTAINER)
204202

205203
$id = strtolower($id);
206204

205+
if ('service_container' === $id) {
206+
// BC: 'service_container' is no longer a self-reference but always
207+
// $this, so ignore this call.
208+
// @todo Throw InvalidArgumentException in next major release.
209+
return;
210+
}
207211
if (self::SCOPE_CONTAINER !== $scope) {
208212
if (!isset($this->scopedServices[$scope])) {
209213
throw new RuntimeException(sprintf('You cannot set service "%s" of inactive scope.', $id));
@@ -240,6 +244,10 @@ public function has($id)
240244
{
241245
$id = strtolower($id);
242246

247+
if ('service_container' === $id) {
248+
return true;
249+
}
250+
243251
return isset($this->services[$id])
244252
|| array_key_exists($id, $this->services)
245253
|| isset($this->aliases[$id])
@@ -276,6 +284,9 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
276284
if ($strtolower) {
277285
$id = strtolower($id);
278286
}
287+
if ('service_container' === $id) {
288+
return $this;
289+
}
279290
if (isset($this->aliases[$id])) {
280291
$id = $this->aliases[$id];
281292
}
@@ -347,6 +358,12 @@ public function initialized($id)
347358
{
348359
$id = strtolower($id);
349360

361+
if ('service_container' === $id) {
362+
// BC: 'service_container' was a synthetic service previously.
363+
// @todo Change to false in next major release.
364+
return true;
365+
}
366+
350367
return isset($this->services[$id]) || array_key_exists($id, $this->services);
351368
}
352369

@@ -364,6 +381,7 @@ public function getServiceIds()
364381
$ids[] = self::underscore($match[1]);
365382
}
366383
}
384+
$ids[] = 'service_container';
367385

368386
return array_unique(array_merge($ids, array_keys($this->services)));
369387
}

0 commit comments

Comments
 (0)