From cbb81da94eed23b1e1e41d61520040d9bf8224dc Mon Sep 17 00:00:00 2001 From: Rastusik Date: Wed, 8 Mar 2023 17:04:40 +0100 Subject: [PATCH] perf(coroutines): removed unnecessary locking in SF container --- .../Symfony/Container/BlockingContainer.php | 15 ++ .../Symfony/Container/ContainerModifier.php | 159 ++++++++++++------ .../ContainerSourceCodeExtractor.php | 34 +++- 3 files changed, 149 insertions(+), 59 deletions(-) diff --git a/src/Bridge/Symfony/Container/BlockingContainer.php b/src/Bridge/Symfony/Container/BlockingContainer.php index 2a5e03e5..2bc3175d 100644 --- a/src/Bridge/Symfony/Container/BlockingContainer.php +++ b/src/Bridge/Symfony/Container/BlockingContainer.php @@ -16,6 +16,11 @@ class BlockingContainer extends Container protected static string $buildContainerNs = ''; + /** + * @var array + */ + protected static array $nonShareableServices = []; + public function __construct(ParameterBagInterface $parameterBag = null) { self::$mutex = (new RecursiveOwnerMutexFactory(new ChannelMutexFactory()))->newMutex(); @@ -28,6 +33,16 @@ public function __construct(ParameterBagInterface $parameterBag = null) */ public function get(string $id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE): ?object { + if (isset(static::$nonShareableServices[$id])) { + return parent::get($id, $invalidBehavior); + } + + $service = $this->services[$id] ?? $this->services[$id = $this->aliases[$id] ?? $id] ?? null; + + if (null !== $service) { + return $service; + } + try { self::$mutex->acquire(); $service = parent::get($id, $invalidBehavior); diff --git a/src/Bridge/Symfony/Container/ContainerModifier.php b/src/Bridge/Symfony/Container/ContainerModifier.php index fb4891a6..ae0e6eea 100644 --- a/src/Bridge/Symfony/Container/ContainerModifier.php +++ b/src/Bridge/Symfony/Container/ContainerModifier.php @@ -24,12 +24,15 @@ public static function modifyContainer(BlockingContainer $container, string $cac return; } - self::overrideGeneratedContainer($reflContainer, $cacheDir, $isDebug); - self::overrideGeneratedContainerGetters($reflContainer, $cacheDir); + $nonShareable = self::overrideGeneratedContainerGetters($reflContainer, $cacheDir); + self::overrideGeneratedContainer($reflContainer, $cacheDir, $isDebug, $nonShareable); self::$alreadyOverridden[$reflContainer->getName()] = true; } - private static function overrideGeneratedContainer(ReflectionClass $reflContainer, string $cacheDir, bool $isDebug): void + /** + * @param array $nonShareable + */ + private static function overrideGeneratedContainer(ReflectionClass $reflContainer, string $cacheDir, bool $isDebug, array $nonShareable): void { $fs = new Filesystem(); $containerFqcn = $reflContainer->getName(); @@ -72,11 +75,27 @@ private static function overrideGeneratedContainer(ReflectionClass $reflContaine continue; } - $methodsCodes[] = self::generateOverriddenGetter($method, $codeExtractor); + $internals = $codeExtractor->getContainerInternalsForMethod($method); + + if (isset($internals['type']) && 'throw' === $internals['type']) { + $internals = []; + } + + if (isset($internals['type']) && 'factories' === $internals['type']) { + $nonShareable[stripslashes($internals['key'])] = true; + $internals = []; + } + + if (empty($internals)) { + continue; + } + + $methodsCodes[] = self::generateOverriddenGetter($method, $internals); } $namespace = $reflContainer->getNamespaceName(); $modifierClassToUse = __CLASS__; + $nonShareableExported = var_export($nonShareable, true); $methodsCode = implode(PHP_EOL.PHP_EOL, $methodsCodes); $newContainerSource = <<dumpFile($cachePath, $replacedContent); } - private static function generateOverriddenGetter(ReflectionMethod $method, ContainerSourceCodeExtractor $extractor): ?string + /** + * @param array{type: string, key: string, key2?: string} $internals + */ + private static function generateOverriddenGetter(ReflectionMethod $method, array $internals): ?string { $methodName = $method->getName(); - $internals = $extractor->getContainerInternalsForMethod($method); if (isset($internals['type']) && 'factories' === $internals['type']) { $internals = []; @@ -219,23 +242,22 @@ private static function generateOverriddenGetter(ReflectionMethod $method, Conta self::generateLazyGetter($methodName, $internals) : self::generateCasualGetter($methodName, $internals); } + /** + * @param array{type: string, key: string, key2?: string} $internals + */ private static function generateLazyGetter(string $methodName, array $internals): string { - $sharedCheck = PHP_EOL; - - if (!empty($internals)) { - $arrayKey = "['{$internals['key']}']".(isset($internals['key2']) ? "['{$internals['key2']}']" : ''); - $sharedCheck = <<{$internals['type']}{$arrayKey})) { - if (\$lazyLoad) { - return \$this->{$internals['type']}{$arrayKey}; - } elseif (isset(\$this->lazyInitializedShared['$methodName'])) { - return \$this->lazyInitializedShared['$methodName']; - } - } - - EOF; - } + $arrayKey = "['{$internals['key']}']".(isset($internals['key2']) ? "['{$internals['key2']}']" : ''); + $sharedCheck = <<{$internals['type']}{$arrayKey})) { + if (\$lazyLoad) { + return \$this->{$internals['type']}{$arrayKey}; + } elseif (isset(\$this->lazyInitializedShared['$methodName'])) { + return \$this->lazyInitializedShared['$methodName']; + } + } + + EOF; return <<acquire(); - {$sharedCheck} + {$sharedCheck} \$return = parent::{$methodName}(\$lazyLoad); @@ -262,27 +284,26 @@ protected function $methodName(\$lazyLoad = true) { EOF; } + /** + * @param array{type: string, key: string, key2?: string} $internals + */ private static function generateCasualGetter(string $methodName, array $internals): string { - $sharedCheck = PHP_EOL; - - if (!empty($internals)) { - $arrayKey = "['{$internals['key']}']".(isset($internals['key2']) ? "['{$internals['key2']}']" : ''); - $sharedCheck = <<{$internals['type']}{$arrayKey})) { - return \$this->{$internals['type']}{$arrayKey}; - } + if (isset(\$this->{$internals['type']}{$arrayKey})) { + return \$this->{$internals['type']}{$arrayKey}; + } - EOF; - } + EOF; return <<acquire(); - {$sharedCheck} + {$sharedCheck} \$return = parent::{$methodName}(); } finally { self::\$mutex->release(); @@ -293,24 +314,36 @@ protected function $methodName() { EOF; } - private static function overrideGeneratedContainerGetters(ReflectionClass $reflContainer, string $cacheDir): void + /** + * @return array + */ + private static function overrideGeneratedContainerGetters(ReflectionClass $reflContainer, string $cacheDir): array { $fs = new Filesystem(); $containerNamespace = $reflContainer->getNamespaceName(); $containerDirectory = $cacheDir.DIRECTORY_SEPARATOR.$containerNamespace; $files = scandir($containerDirectory); $filteredFiles = array_filter($files, fn (string $fileName): bool => str_starts_with($fileName, 'get')); + $nonShareable = []; foreach ($filteredFiles as $fileName) { $class = str_replace('.php', '', $fileName); - self::generateOverriddenDoInExtension( + $nonShareableSvcId = self::generateOverriddenDoInExtension( $fs, $containerDirectory, $fileName, $class, $containerNamespace ); + + if ($nonShareableSvcId === null) { + continue; + } + + $nonShareable[$nonShareableSvcId] = true; } + + return $nonShareable; } private static function generateOverriddenDoInExtension( @@ -319,17 +352,17 @@ private static function generateOverriddenDoInExtension( string $fileToLoad, string $class, string $namespace - ): void { + ): ?string { $fullPath = $containerDir.\DIRECTORY_SEPARATOR.$fileToLoad; if (str_contains($fullPath, '__Overridden.php') || str_contains($class, '__Overridden')) { - return; + return null; } $fullOverriddenPath = str_replace('.php', '__Overridden.php', $fullPath); if (file_exists($fullOverriddenPath)) { - return; + return null; } $overriddenClass = $class.'__Overridden'; @@ -338,29 +371,44 @@ private static function generateOverriddenDoInExtension( $codeExtractor = new ContainerSourceCodeExtractor($origContent); $overriddenContent = str_replace($class, $overriddenClass, $origContent); $overriddenContent = str_replace('self::do(', 'static::do(', $overriddenContent); - $fs->rename($fullPath, $fullOverriddenPath, true); + $fs->copy($fullPath, $fullOverriddenPath, true); $fs->dumpFile($fullOverriddenPath, $overriddenContent); require_once $fullOverriddenPath; $reflClass = new ReflectionClass($overriddenFqcn); $reflMethod = $reflClass->getMethod('do'); $codeExtractor = new ContainerSourceCodeExtractor($overriddenContent); $internals = $codeExtractor->getContainerInternalsForMethod($reflMethod, true); - $sharedCheck = ''; - - if (!empty($internals)) { - $arrayKey = "['{$internals['key']}']".(isset($internals['key2']) ? "['{$internals['key2']}']" : ''); - $sharedCheck = <<{$internals['type']}{$arrayKey})) { - if (\$lazyLoad) { - return \$container->{$internals['type']}{$arrayKey}; - } elseif (isset(\$container->lazyInitializedShared['$overriddenClass'])) { - return \$container->lazyInitializedShared['$overriddenClass']; - } - } + $nonShareableSvcId = null; - EOF; + if (isset($internals['type']) && 'throw' === $internals['type']) { + $internals = []; } + if (isset($internals['type']) && 'factories' === $internals['type']) { + $nonShareableSvcId = stripslashes($internals['key']); + $internals = []; + } + + if (empty($internals)) { + $fs->remove($overriddenFqcn); + + return $nonShareableSvcId; + } + + $fs->remove($fullPath); + $arrayKey = "['{$internals['key']}']".(isset($internals['key2']) ? "['{$internals['key2']}']" : ''); + $sharedCheck = <<{$internals['type']}{$arrayKey})) { + if (\$lazyLoad) { + return \$container->{$internals['type']}{$arrayKey}; + } elseif (isset(\$container->lazyInitializedShared['$overriddenClass'])) { + return \$container->lazyInitializedShared['$overriddenClass']; + } + } + + EOF; + $newContent = <<dumpFile($fullPath, $newContent); - require_once $fullOverriddenPath; require_once $fullPath; + + return null; } private static function getIgnoredGetters(): array diff --git a/src/Bridge/Symfony/Container/ContainerSourceCodeExtractor.php b/src/Bridge/Symfony/Container/ContainerSourceCodeExtractor.php index 2801aec8..451261e6 100644 --- a/src/Bridge/Symfony/Container/ContainerSourceCodeExtractor.php +++ b/src/Bridge/Symfony/Container/ContainerSourceCodeExtractor.php @@ -15,20 +15,46 @@ public function __construct(string $sourceCode) $this->sourceCode = explode(PHP_EOL, $sourceCode); } + /** + * @return array{}|array{type: string, key: string, key2?: string} + */ public function getContainerInternalsForMethod(ReflectionMethod $method, bool $isExtension = false): array { $code = $this->getMethodCode($method); $variable = $isExtension ? 'container' : 'this'; - if (!preg_match( - '/return \\$'.$variable.'->(?P[a-z]+)\[\'(?P[^\']+)\'\](\[\'(?P[^\']+)\'\])? \=/', + if (preg_match( + '/return \\$'.$variable.'->(?P[a-z]+)\[\'(?P[^\']+)\'\](\[\'(?P[^\']+)\'\])?((\(\))|( \=))/', $code, $matches )) { - return []; + if ($matches['key2'] === '') { + unset($matches['key2']); + } + + return $matches; + } + + if (preg_match( + '/\\$'.$variable.'->(?P[a-z]+)\[\'(?P[^\']+)\'\] \= \\$instance/', + $code, + $matches + )) { + return $matches; + } + + if (preg_match( + '/\\$'.$variable.'->throw\(/', + $code, + $matches + )) { + $matches['type'] = 'throw'; + $matches['key'] = 'nevermind'; + + return $matches; } - return $matches; + return []; } public function getMethodCode(ReflectionMethod $method): string