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

Skip to content

Commit 770425c

Browse files
committed
Prevent infinite nesting of lazy ObjectManager instances when ObjectManager is reset
This patch ensures that the `ObjectManager` that the `Symfony\Bridge\Doctrine\ManagerRegistry` replaces during `Symfony\Bridge\Doctrine\ManagerRegistry#resetService()` operations is a fresh non-lazy service. Before this change, `Symfony\Bridge\Doctrine\ManagerRegistry#resetService()` would replace the initialization of the lazy proxy with a new service each time, but that service being lazy, this led to an additional proxy nesting level at each service reset call. That leads to general issues around memory reliability, stack trace nesting (and therefore bigger logged traces) and potentially even stack overflow problems, when running with XDebug. The problem seems to only apply when the `symfony/dependency-injection` `Container` is compiled as a set of small factory files: that's because each generated factory has a boolean flag that indicates whether a lazy or non-lazy version of a service is requested, as introduced in the original implementation at #7890
1 parent daba023 commit 770425c

File tree

2 files changed

+94
-1
lines changed

2 files changed

+94
-1
lines changed

src/Symfony/Bridge/Doctrine/ManagerRegistry.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function (&$wrappedInstance, LazyLoadingInterface $manager) use ($name) {
6262
$name = $this->aliases[$name];
6363
}
6464
if (isset($this->fileMap[$name])) {
65-
$wrappedInstance = $this->load($this->fileMap[$name]);
65+
$wrappedInstance = $this->load($this->fileMap[$name], false);
6666
} else {
6767
$method = $this->methodMap[$name] ?? 'get'.strtr($name, $this->underscoreMap).'Service'; // BC with DI v3.4
6868
$wrappedInstance = $this->{$method}(false);

src/Symfony/Bridge/Doctrine/Tests/ManagerRegistryTest.php

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,15 @@
1212
namespace Symfony\Bridge\Doctrine\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use ProxyManager\Proxy\LazyLoadingInterface;
16+
use ProxyManager\Proxy\ValueHolderInterface;
1517
use Symfony\Bridge\Doctrine\ManagerRegistry;
18+
use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper;
1619
use Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper\PhpDumperTest;
20+
use Symfony\Component\DependencyInjection\ContainerBuilder;
21+
use Symfony\Component\DependencyInjection\ContainerInterface;
22+
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
23+
use Symfony\Component\Filesystem\Filesystem;
1724

1825
class ManagerRegistryTest extends TestCase
1926
{
@@ -39,6 +46,92 @@ public function testResetService()
3946
$this->assertSame($foo, $container->get('foo'));
4047
$this->assertObjectNotHasAttribute('bar', $foo);
4148
}
49+
50+
/**
51+
* When performing an entity manager lazy service reset, the reset operations may re-use the container
52+
* to create a "fresh" service: when doing so, it can happen that the "fresh" service is itself a proxy.
53+
*
54+
* Because of that, the proxy will be populated with a wrapped value that is itself a proxy: repeating
55+
* the reset operation keeps increasing this nesting until the application eventually runs into stack
56+
* overflow or memory overflow operations, which can happen for long-running processes that rely on
57+
* services that are reset very often.
58+
*/
59+
public function testResetServiceWillNotNestFurtherLazyServicesWithinEachOther()
60+
{
61+
// This test scenario only applies to containers composed as a set of generated sources
62+
$this->dumpLazyServiceProjectAsFilesServiceContainer();
63+
64+
/** @var ContainerInterface $container */
65+
$container = new \LazyServiceProjectAsFilesServiceContainer();
66+
67+
$registry = new TestManagerRegistry(
68+
'irrelevant',
69+
[],
70+
['defaultManager' => 'foo'],
71+
'irrelevant',
72+
'defaultManager',
73+
'irrelevant'
74+
);
75+
$registry->setTestContainer($container);
76+
77+
$service = $container->get('foo');
78+
79+
self::assertInstanceOf(\stdClass::class, $service);
80+
self::assertInstanceOf(LazyLoadingInterface::class, $service);
81+
self::assertInstanceOf(ValueHolderInterface::class, $service);
82+
self::assertFalse($service->isProxyInitialized());
83+
84+
$service->initializeProxy();
85+
86+
self::assertTrue($container->initialized('foo'));
87+
self::assertTrue($service->isProxyInitialized());
88+
89+
$registry->resetManager();
90+
$service->initializeProxy();
91+
92+
$wrappedValue = $service->getWrappedValueHolderValue();
93+
self::assertInstanceOf(\stdClass::class, $wrappedValue);
94+
self::assertNotInstanceOf(LazyLoadingInterface::class, $wrappedValue);
95+
self::assertNotInstanceOf(ValueHolderInterface::class, $wrappedValue);
96+
}
97+
98+
/** @return void */
99+
private function dumpLazyServiceProjectAsFilesServiceContainer()
100+
{
101+
if (class_exists(\LazyServiceProjectAsFilesServiceContainer::class, false)) {
102+
return;
103+
}
104+
105+
$container = new ContainerBuilder();
106+
107+
$container->register('foo', \stdClass::class)
108+
->setPublic(true)
109+
->setLazy(true);
110+
$container->compile();
111+
112+
$fileSystem = new Filesystem();
113+
114+
$temporaryPath = $fileSystem->tempnam(sys_get_temp_dir(), 'symfonyManagerRegistryTest');
115+
$fileSystem->remove($temporaryPath);
116+
$fileSystem->mkdir($temporaryPath);
117+
118+
$dumper = new PhpDumper($container);
119+
120+
$dumper->setProxyDumper(new ProxyDumper());
121+
$containerFiles = $dumper->dump([
122+
'class' => 'LazyServiceProjectAsFilesServiceContainer',
123+
'as_files' => true,
124+
]);
125+
126+
array_walk(
127+
$containerFiles,
128+
static function (string $containerSources, string $fileName) use ($temporaryPath): void {
129+
(new Filesystem())->dumpFile($temporaryPath.'/'.$fileName, $containerSources);
130+
}
131+
);
132+
133+
require $temporaryPath.'/LazyServiceProjectAsFilesServiceContainer.php';
134+
}
42135
}
43136

44137
class TestManagerRegistry extends ManagerRegistry

0 commit comments

Comments
 (0)