Skip to content

Commit

Permalink
perf(coroutines): removed unnecessary locking in SF container
Browse files Browse the repository at this point in the history
  • Loading branch information
Rastusik committed Mar 8, 2023
1 parent b86c8e4 commit cbb81da
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 59 deletions.
15 changes: 15 additions & 0 deletions src/Bridge/Symfony/Container/BlockingContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ class BlockingContainer extends Container

protected static string $buildContainerNs = '';

/**
* @var array<string, bool>
*/
protected static array $nonShareableServices = [];

public function __construct(ParameterBagInterface $parameterBag = null)
{
self::$mutex = (new RecursiveOwnerMutexFactory(new ChannelMutexFactory()))->newMutex();
Expand All @@ -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);
Expand Down
159 changes: 104 additions & 55 deletions src/Bridge/Symfony/Container/ContainerModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, bool> $nonShareable
*/
private static function overrideGeneratedContainer(ReflectionClass $reflContainer, string $cacheDir, bool $isDebug, array $nonShareable): void
{
$fs = new Filesystem();
$containerFqcn = $reflContainer->getName();
Expand Down Expand Up @@ -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 = <<<EOF
<?php
Expand All @@ -87,6 +106,8 @@ private static function overrideGeneratedContainer(ReflectionClass $reflContaine
class $containerClass extends $overriddenClass
{
protected static array \$nonShareableServices = $nonShareableExported;
protected \$lazyInitializedShared = [];
$methodsCode
Expand Down Expand Up @@ -206,10 +227,12 @@ private static function overrideCachedEntrypoint(Filesystem $fs, string $cacheDi
$fs->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 = [];
Expand All @@ -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 = <<<EOF
if (isset(\$this->{$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 = <<<EOF
if (isset(\$this->{$internals['type']}{$arrayKey})) {
if (\$lazyLoad) {
return \$this->{$internals['type']}{$arrayKey};
} elseif (isset(\$this->lazyInitializedShared['$methodName'])) {
return \$this->lazyInitializedShared['$methodName'];
}
}
EOF;

return <<<EOF
protected function $methodName(\$lazyLoad = true) {
Expand All @@ -245,10 +267,10 @@ protected function $methodName(\$lazyLoad = true) {
\$lazyLoad = true;
}
{$sharedCheck}
{$sharedCheck}
try {
self::\$mutex->acquire();
{$sharedCheck}
{$sharedCheck}
\$return = parent::{$methodName}(\$lazyLoad);
Expand All @@ -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 = <<<EOF
$arrayKey = "['{$internals['key']}']".(isset($internals['key2']) ? "['{$internals['key2']}']" : '');
$sharedCheck = <<<EOF
if (isset(\$this->{$internals['type']}{$arrayKey})) {
return \$this->{$internals['type']}{$arrayKey};
}
if (isset(\$this->{$internals['type']}{$arrayKey})) {
return \$this->{$internals['type']}{$arrayKey};
}
EOF;
}
EOF;

return <<<EOF
protected function $methodName() {
{$sharedCheck}
{$sharedCheck}
try {
self::\$mutex->acquire();
{$sharedCheck}
{$sharedCheck}
\$return = parent::{$methodName}();
} finally {
self::\$mutex->release();
Expand All @@ -293,24 +314,36 @@ protected function $methodName() {
EOF;
}

private static function overrideGeneratedContainerGetters(ReflectionClass $reflContainer, string $cacheDir): void
/**
* @return array<string, bool>
*/
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(
Expand All @@ -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';
Expand All @@ -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 = <<<EOF
if (isset(\$container->{$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 = <<<EOF
if (isset(\$container->{$internals['type']}{$arrayKey})) {
if (\$lazyLoad) {
return \$container->{$internals['type']}{$arrayKey};
} elseif (isset(\$container->lazyInitializedShared['$overriddenClass'])) {
return \$container->lazyInitializedShared['$overriddenClass'];
}
}
EOF;

$newContent = <<<EOF
<?php
Expand Down Expand Up @@ -392,8 +440,9 @@ public static function do(\$container, \$lazyLoad = true)
EOF;
$fs->dumpFile($fullPath, $newContent);

require_once $fullOverriddenPath;
require_once $fullPath;

return null;
}

private static function getIgnoredGetters(): array
Expand Down
34 changes: 30 additions & 4 deletions src/Bridge/Symfony/Container/ContainerSourceCodeExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<type>[a-z]+)\[\'(?P<key>[^\']+)\'\](\[\'(?P<key2>[^\']+)\'\])? \=/',
if (preg_match(
'/return \\$'.$variable.'->(?P<type>[a-z]+)\[\'(?P<key>[^\']+)\'\](\[\'(?P<key2>[^\']+)\'\])?((\(\))|( \=))/',
$code,
$matches
)) {
return [];
if ($matches['key2'] === '') {
unset($matches['key2']);
}

return $matches;
}

if (preg_match(
'/\\$'.$variable.'->(?P<type>[a-z]+)\[\'(?P<key>[^\']+)\'\] \= \\$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
Expand Down

0 comments on commit cbb81da

Please sign in to comment.