Skip to content

Commit 526fd9f

Browse files
committed
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 | - #32764 (comment) 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 ------- 0b5b3ed [HttpKernel] wrap compilation of the container in an opportunistic lock
2 parents 5b7848c + 0b5b3ed commit 526fd9f

File tree

1 file changed

+64
-27
lines changed

1 file changed

+64
-27
lines changed

src/Symfony/Component/HttpKernel/Kernel.php

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -505,25 +505,72 @@ protected function initializeContainer()
505505
$class = $this->getContainerClass();
506506
$cacheDir = $this->warmupDir ?: $this->getCacheDir();
507507
$cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
508-
$oldContainer = null;
509-
if ($fresh = $cache->isFresh()) {
510-
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
511-
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
512-
$fresh = $oldContainer = false;
513-
try {
514-
if (file_exists($cache->getPath()) && \is_object($this->container = include $cache->getPath())) {
515-
$this->container->set('kernel', $this);
516-
$oldContainer = $this->container;
517-
$fresh = true;
518-
}
519-
} catch (\Throwable $e) {
520-
} finally {
508+
$cachePath = $cache->getPath();
509+
510+
// Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors
511+
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
512+
513+
try {
514+
if (file_exists($cachePath) && \is_object($this->container = include $cachePath) && (!$this->debug || $cache->isFresh())) {
515+
$this->container->set('kernel', $this);
521516
error_reporting($errorLevel);
517+
518+
return;
522519
}
520+
} catch (\Throwable $e) {
523521
}
524522

525-
if ($fresh) {
526-
return;
523+
$oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : $this->container = null;
524+
525+
try {
526+
is_dir($cacheDir) ?: mkdir($cacheDir, 0777, true);
527+
528+
if ($lock = fopen($cachePath, 'w')) {
529+
chmod($cachePath, 0666 & ~umask());
530+
flock($lock, LOCK_EX | LOCK_NB, $wouldBlock);
531+
532+
if (!flock($lock, $wouldBlock ? LOCK_SH : LOCK_EX)) {
533+
fclose($lock);
534+
} else {
535+
$cache = new class($cachePath, $this->debug) extends ConfigCache {
536+
public $lock;
537+
538+
public function write($content, array $metadata = null)
539+
{
540+
rewind($this->lock);
541+
ftruncate($this->lock, 0);
542+
fwrite($this->lock, $content);
543+
544+
if (null !== $metadata) {
545+
file_put_contents($this->getPath().'.meta', serialize($metadata));
546+
@chmod($this->getPath().'.meta', 0666 & ~umask());
547+
}
548+
549+
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
550+
opcache_invalidate($this->getPath(), true);
551+
}
552+
}
553+
554+
public function __destruct()
555+
{
556+
flock($this->lock, LOCK_UN);
557+
fclose($this->lock);
558+
}
559+
};
560+
$cache->lock = $lock;
561+
562+
if (!\is_object($this->container = include $cachePath)) {
563+
$this->container = null;
564+
} elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) {
565+
$this->container->set('kernel', $this);
566+
567+
return;
568+
}
569+
}
570+
}
571+
} catch (\Throwable $e) {
572+
} finally {
573+
error_reporting($errorLevel);
527574
}
528575

529576
if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
@@ -581,19 +628,9 @@ protected function initializeContainer()
581628
}
582629
}
583630

584-
if (null === $oldContainer && file_exists($cache->getPath())) {
585-
$errorLevel = error_reporting(\E_ALL ^ \E_WARNING);
586-
try {
587-
$oldContainer = include $cache->getPath();
588-
} catch (\Throwable $e) {
589-
} finally {
590-
error_reporting($errorLevel);
591-
}
592-
}
593-
$oldContainer = \is_object($oldContainer) ? new \ReflectionClass($oldContainer) : false;
594-
595631
$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
596-
$this->container = require $cache->getPath();
632+
unset($cache);
633+
$this->container = require $cachePath;
597634
$this->container->set('kernel', $this);
598635

599636
if ($oldContainer && \get_class($this->container) !== $oldContainer->name) {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy