feature #33701 [HttpKernel] wrap compilation of the container in an opportunistic lock (nicolas-grekas)
This PR was merged into the 4.4 branch.
Discussion
----------
[HttpKernel] wrap compilation of the container in an opportunistic lock
| Q | A
| ------------- | ---
| Branch? | 4.4
| Bug fix? | no
| New feature? | yes
| Deprecations? | no
| Tickets | -
| License | MIT
| Doc PR | -
https://github.com/symfony/symfony/pull/32764#issuecomment-516924305
This PR adds a lock around the compilation of the container. When two or more concurrent requests want to compile the container, the first one runs the computation and the others wait for its completion. If for any reasons the lock doesn't work, compilation happens as usual.
The effect is visible when developing locally:
Here is what all concurrent requests consume now:
![image](https://user-images.githubusercontent.com/243674/65603626-4e231d00-dfa6-11e9-8b6c-62dbd5eb30fe.png)
And here is what they will consume with this PR (they wait but reuse the just compiled container):
![image](https://user-images.githubusercontent.com/243674/65603733-7f9be880-dfa6-11e9-930b-ce793c3e280c.png)
Commits
-------
0b5b3ed7f9
[HttpKernel] wrap compilation of the container in an opportunistic lock
This commit is contained in:
commit
526fd9fc44
@ -505,25 +505,72 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
|
||||
$class = $this->getContainerClass();
|
||||
$cacheDir = $this->warmupDir ?: $this->getCacheDir();
|
||||
$cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
|
||||
$oldContainer = null;
|
||||
if ($fresh = $cache->isFresh()) {
|
||||
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
|
||||
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
|
||||
$fresh = $oldContainer = false;
|
||||
try {
|
||||
if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) {
|
||||
$this->container->set('kernel', $this);
|
||||
$oldContainer = $this->container;
|
||||
$fresh = true;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} finally {
|
||||
$cachePath = $cache->getPath();
|
||||
|
||||
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
|
||||
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
|
||||
|
||||
try {
|
||||
if (file_exists($cachePath) && \is_object($this->container = include $cachePath) && (!$this->debug || $cache->isFresh())) {
|
||||
$this->container->set('kernel', $this);
|
||||
error_reporting($errorLevel);
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
if ($fresh) {
|
||||
return;
|
||||
$oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : $this->container = null;
|
||||
|
||||
try {
|
||||
is_dir($cacheDir) ?: mkdir($cacheDir, 0777, true);
|
||||
|
||||
if ($lock = fopen($cachePath, 'w')) {
|
||||
chmod($cachePath, 0666 & ~umask());
|
||||
flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
|
||||
|
||||
if (!flock($lock, $wouldBlock ? LOCK_SH : LOCK_EX)) {
|
||||
fclose($lock);
|
||||
} else {
|
||||
$cache = new class($cachePath, $this->debug) extends ConfigCache {
|
||||
public $lock;
|
||||
|
||||
public function write($content, array $metadata = null)
|
||||
{
|
||||
rewind($this->lock);
|
||||
ftruncate($this->lock, 0);
|
||||
fwrite($this->lock, $content);
|
||||
|
||||
if (null !== $metadata) {
|
||||
file_put_contents($this->getPath().'.meta', serialize($metadata));
|
||||
@chmod($this->getPath().'.meta', 0666 & ~umask());
|
||||
}
|
||||
|
||||
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
|
||||
opcache_invalidate($this->getPath(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
flock($this->lock, LOCK_UN);
|
||||
fclose($this->lock);
|
||||
}
|
||||
};
|
||||
$cache->lock = $lock;
|
||||
|
||||
if (!\is_object($this->container = include $cachePath)) {
|
||||
$this->container = null;
|
||||
} elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) {
|
||||
$this->container->set('kernel', $this);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} finally {
|
||||
error_reporting($errorLevel);
|
||||
}
|
||||
|
||||
if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
|
||||
@ -581,19 +628,9 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $oldContainer && file_exists($cache->getPath())) {
|
||||
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
|
||||
try {
|
||||
$oldContainer = include $cache->getPath();
|
||||
} catch (\Throwable $e) {
|
||||
} finally {
|
||||
error_reporting($errorLevel);
|
||||
}
|
||||
}
|
||||
$oldContainer = \is_object($oldContainer) ? new \ReflectionClass($oldContainer) : false;
|
||||
|
||||
$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
|
||||
$this->container = require $cache->getPath();
|
||||
unset($cache);
|
||||
$this->container = require $cachePath;
|
||||
$this->container->set('kernel', $this);
|
||||
|
||||
if ($oldContainer && \get_class($this->container) !== $oldContainer->name) {
|
||||
|
Reference in New Issue
Block a user