From ff20a76b3cf4e7bc0bcb13b9ab2be51e7cce9570 Mon Sep 17 00:00:00 2001 From: Rastusik Date: Mon, 16 Jan 2023 17:47:09 +0100 Subject: [PATCH] fix(coroutines): prod container generation imlemented using generated container class instead of z-engine because of segmentation faults --- .../Symfony/Container/ContainerModifier.php | 156 ++++++++++++------ .../CoroutinesSupportingKernelTrait.php | 18 +- tests/Fixtures/Symfony/TestAppKernel.php | 26 +-- tests/Fixtures/Symfony/app/console | 7 +- 4 files changed, 142 insertions(+), 65 deletions(-) diff --git a/src/Bridge/Symfony/Container/ContainerModifier.php b/src/Bridge/Symfony/Container/ContainerModifier.php index a976fe0e..85828ab8 100644 --- a/src/Bridge/Symfony/Container/ContainerModifier.php +++ b/src/Bridge/Symfony/Container/ContainerModifier.php @@ -4,7 +4,9 @@ namespace K911\Swoole\Bridge\Symfony\Container; +use Symfony\Component\Config\ConfigCache; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\Filesystem\Filesystem; use ZEngine\Reflection\ReflectionClass; use ZEngine\Reflection\ReflectionMethod; @@ -12,20 +14,42 @@ final class ContainerModifier { private static $alreadyOverridden = []; - public static function modifyContainer(BlockingContainer $container): void + public static function includeOverriddenContainer(string $cacheDir, string $containerClass, bool $isDebug): void { - $reflContainer = new ReflectionClass($container); - BlockingContainer::setBuildContainerNs($reflContainer->getNamespaceName()); + $cache = new ConfigCache($cacheDir.'/'.$containerClass.'.php', $isDebug); + $cachePath = $cache->getPath(); - if (isset(self::$alreadyOverridden[$reflContainer->getName()])) { + if (!file_exists($cachePath)) { return; } - if (!$reflContainer->hasMethod('createProxy')) { + $content = file_get_contents($cachePath); + $found = preg_match('/(?P\\\Container.*)::/', $content, $matches); + + if (!$found || !isset($matches['class'])) { + throw new \UnexpectedValueException(sprintf('Container class missing in file %s', $cachePath)); + } + + $overriddenFqcn = $matches['class'].'_Overridden'; + $overriddenFile = $cacheDir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $overriddenFqcn).'.php'; + + if (!file_exists($overriddenFile)) { + return; + } + + require_once $overriddenFile; + } + + public static function modifyContainer(BlockingContainer $container, string $cacheDir): void + { + $reflContainer = new ReflectionClass($container); + BlockingContainer::setBuildContainerNs($reflContainer->getNamespaceName()); + + if (isset(self::$alreadyOverridden[$reflContainer->getName()])) { return; } - self::overrideGetters($container, $reflContainer); + self::overrideGeneratedContainer($reflContainer, $cacheDir); self::overrideCreateProxy($container, $reflContainer); self::overrideLoad($container, $reflContainer); self::$alreadyOverridden[$reflContainer->getName()] = true; @@ -77,6 +101,10 @@ public static function getIgnoredGetters(): array private static function overrideCreateProxy(BlockingContainer $container, ReflectionClass $reflContainer): void { + if (!$reflContainer->hasMethod('createProxy')) { + return; + } + $createProxyRefl = $reflContainer->getMethod('createProxy'); $reflContainer->addMethod('createProxyOverridden', $createProxyRefl->getClosure($container)); $createProxyRefl->redefine(function ($class, \Closure $factory) { @@ -151,10 +179,26 @@ private static function overrideGeneratedLoad(BlockingContainer $container, Refl }); } - private static function overrideGetters(BlockingContainer $container, ReflectionClass $reflContainer): void + private static function overrideGeneratedContainer(ReflectionClass $reflContainer, string $cacheDir): void { + $fs = new Filesystem(); + $containerFqcn = $reflContainer->getName(); + $overriddenFqcn = $containerFqcn.'_Overridden'; + $classParts = explode('\\', $containerFqcn); + $containerClass = array_pop($classParts); + $overriddenClass = $containerClass.'_Overridden'; + $containerFile = $cacheDir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $containerFqcn).'.php'; + $overriddenFile = $cacheDir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $overriddenFqcn).'.php'; + + if (file_exists($overriddenFile)) { + return; + } + + $containerSource = file_get_contents($containerFile); + $overriddenSource = str_replace('class '.$containerClass, 'class '.$overriddenClass, $containerSource); $ignoredMethods = self::getIgnoredGetters(); $methods = $reflContainer->getMethods(\ReflectionMethod::IS_PROTECTED); + $methodsCodes = []; foreach ($methods as $method) { $methodName = $method->getName(); @@ -163,58 +207,72 @@ private static function overrideGetters(BlockingContainer $container, Reflection continue; } - self::overrideGetter($container, $reflContainer, $method); + $methodsCodes[] = self::generateOverriddenGetter($method); } - } - - private static function overrideGetter(BlockingContainer $container, ReflectionClass $reflContainer, ReflectionMethod $method): void - { - $methodName = $method->getName(); - $overriddenMethodName = self::getOverriddenGetterName($methodName); - $reflContainer->addMethod($overriddenMethodName, $method->getClosure($container)); - $newGetter = $method->getNumberOfParameters() > 0 ? self::createLazyGetter() : self::createCasualGetter(); - $method->redefine($newGetter); - } - private static function createLazyGetter(): \Closure - { - return function ($lazyLoad = true) { - // this might be a weird SF container bug or idk... but SF container keeps calling this factory method - // with service id - if (is_string($lazyLoad)) { - $lazyLoad = true; - } + $namespace = $reflContainer->getNamespaceName(); + $methodsCode = implode(PHP_EOL.PHP_EOL, $methodsCodes); + $newContainerSource = <<acquire($overriddenMethodName.'_'.($lazyLoad ? 'lazy' : '')); + namespace $namespace; - try { - $return = $this->{$overriddenMethodName}($lazyLoad); - } finally { - $lock->release(); + class $containerClass extends $overriddenClass + { + $methodsCode } + EOF; - return $return; - }; + $fs->copy($containerFile, $overriddenFile); + $fs->dumpFile($overriddenFile, $overriddenSource); + $fs->dumpFile($containerFile, $newContainerSource); } - private static function createCasualGetter(): \Closure + private static function generateOverriddenGetter(ReflectionMethod $method): string { - return function () { - $overriddenMethodName = ContainerModifier::getOverriddenGetterName( - debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function'] - ); - $lock = self::$locking->acquire($overriddenMethodName); + $methodName = $method->getName(); - try { - $return = $this->{$overriddenMethodName}(); - } finally { - $lock->release(); - } + return $method->getNumberOfParameters() > 0 ? + self::generateLazyGetter($methodName) : self::generateCasualGetter($methodName); + } - return $return; - }; + private static function generateLazyGetter(string $methodName): string + { + return <<acquire('$methodName'.'_'.(\$lazyLoad ? 'lazy' : '')); + + try { + \$return = parent::{$methodName}(\$lazyLoad); + } finally { + \$lock->release(); + } + + return \$return; + } + EOF; + } + + private static function generateCasualGetter(string $methodName): string + { + return <<acquire('$methodName'); + + try { + \$return = parent::{$methodName}(); + } finally { + \$lock->release(); + } + + return \$return; + } + EOF; } } diff --git a/src/Bridge/Symfony/Kernel/CoroutinesSupportingKernelTrait.php b/src/Bridge/Symfony/Kernel/CoroutinesSupportingKernelTrait.php index b17b0787..d89570d9 100644 --- a/src/Bridge/Symfony/Kernel/CoroutinesSupportingKernelTrait.php +++ b/src/Bridge/Symfony/Kernel/CoroutinesSupportingKernelTrait.php @@ -36,17 +36,29 @@ protected function getContainerBaseClass(): string protected function initializeContainer() { FinalClassModifier::initialize($this->getCacheDir()); + $cacheDir = $this->getCacheDir(); + $containerClass = $this->getContainerClass(); + ContainerModifier::includeOverriddenContainer($cacheDir, $containerClass, $this->isDebug()); parent::initializeContainer(); - if (!$this->container->hasParameter(ContainerConstants::PARAM_COROUTINES_ENABLED)) { + if (!$this->areCoroutinesEnabled()) { return; } + ContainerModifier::modifyContainer($this->container, $cacheDir); + } + + private function areCoroutinesEnabled(): bool + { + if (!$this->container->hasParameter(ContainerConstants::PARAM_COROUTINES_ENABLED)) { + return false; + } + if (!$this->container->getParameter(ContainerConstants::PARAM_COROUTINES_ENABLED)) { - return; + return false; } - ContainerModifier::modifyContainer($this->container); + return true; } } diff --git a/tests/Fixtures/Symfony/TestAppKernel.php b/tests/Fixtures/Symfony/TestAppKernel.php index b7883e48..88e46266 100644 --- a/tests/Fixtures/Symfony/TestAppKernel.php +++ b/tests/Fixtures/Symfony/TestAppKernel.php @@ -31,13 +31,15 @@ class TestAppKernel extends Kernel private const CONFIG_EXTENSIONS = '.{php,xml,yaml,yml}'; - private $cacheKernel; + private ?string $overrideProdEnv; - private $coverageEnabled; + private ?TestCacheKernel $cacheKernel = null; - private $profilerEnabled = false; + private bool $coverageEnabled; - public function __construct(string $environment, bool $debug) + private bool $profilerEnabled = false; + + public function __construct(string $environment, bool $debug, ?string $overrideProdEnv = null) { if ('_cov' === \mb_substr($environment, -4, 4)) { $environment = \mb_substr($environment, 0, -4); @@ -57,6 +59,12 @@ public function __construct(string $environment, bool $debug) $this->cacheKernel = new TestCacheKernel($this); } + if (null !== $overrideProdEnv) { + $overrideProdEnv = trim($overrideProdEnv); + } + + $this->overrideProdEnv = $overrideProdEnv; + parent::__construct($environment, $debug); } @@ -182,17 +190,11 @@ private function getVarDir(): string private function loadOverrideForProdEnvironment(string $confDir, LoaderInterface $loader): void { - if (!isset($_SERVER['OVERRIDE_PROD_ENV'])) { - return; - } - - $overrideEnv = trim((string) $_SERVER['OVERRIDE_PROD_ENV']); - - if ('' === $overrideEnv) { + if ('prod' !== $this->environment) { return; } - $envPackageConfigurationDir = sprintf('%s/%s', $confDir, $overrideEnv); + $envPackageConfigurationDir = sprintf('%s/%s', $confDir, $this->overrideProdEnv); if (!is_dir($envPackageConfigurationDir)) { return; diff --git a/tests/Fixtures/Symfony/app/console b/tests/Fixtures/Symfony/app/console index ac89b270..f817d7a7 100755 --- a/tests/Fixtures/Symfony/app/console +++ b/tests/Fixtures/Symfony/app/console @@ -16,6 +16,7 @@ if (!\class_exists(Application::class)) { $input = new ArgvInput(); $env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'test'); +$overrideProdEnv = $input->getParameterOption(['--override-prod-env', '-o'], $_SERVER['OVERRIDE_PROD_ENV'] ?? null); $debug = ($_SERVER['APP_DEBUG'] ?? ('prod' !== $env)) && !$input->hasParameterOption(['--no-debug', '']); if ($debug) { @@ -26,6 +27,10 @@ if ($debug) { } } -$kernel = new TestAppKernel($env, $debug); +$argv = $_SERVER['argv']; +$argv = array_filter($argv, fn (string $arg): bool => strpos($arg, '--override-prod-env') === false && strpos($arg, '-o ') === false); +$input = new ArgvInput($argv); + +$kernel = new TestAppKernel($env, $debug, $overrideProdEnv); $application = new Application($kernel); $application->run($input);