Skip to content

Commit

Permalink
fix(coroutines): prod container generation imlemented using generated…
Browse files Browse the repository at this point in the history
… container class instead of z-engine because of segmentation faults
  • Loading branch information
Rastusik committed Feb 6, 2023
1 parent e8365e2 commit ff20a76
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 65 deletions.
156 changes: 107 additions & 49 deletions src/Bridge/Symfony/Container/ContainerModifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,52 @@

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;

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<class>\\\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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -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 = <<<EOF
<?php
$overriddenMethodName = ContainerModifier::getOverriddenGetterName(
debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['function']
);
$lock = self::$locking->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 <<<EOF
protected function $methodName(\$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;
}
\$lock = self::\$locking->acquire('$methodName'.'_'.(\$lazyLoad ? 'lazy' : ''));
try {
\$return = parent::{$methodName}(\$lazyLoad);
} finally {
\$lock->release();
}
return \$return;
}
EOF;
}

private static function generateCasualGetter(string $methodName): string
{
return <<<EOF
protected function $methodName() {
\$lock = self::\$locking->acquire('$methodName');
try {
\$return = parent::{$methodName}();
} finally {
\$lock->release();
}
return \$return;
}
EOF;
}
}
18 changes: 15 additions & 3 deletions src/Bridge/Symfony/Kernel/CoroutinesSupportingKernelTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
26 changes: 14 additions & 12 deletions tests/Fixtures/Symfony/TestAppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion tests/Fixtures/Symfony/app/console
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);

0 comments on commit ff20a76

Please sign in to comment.