From f3ef6ea7cbeda18fea3ed511be4efae88b248e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 15 Oct 2026 15:00:44 +0200 Subject: [PATCH 1/4] feat(franken): Do not rebuild Server object between requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the container and app containers are dropped, the server object itself is kept. That will allow to keep global state in the Server object if needed, and later to keep or revive some services. Signed-off-by: Côme Chilliet --- lib/OC.php | 6 +++++- lib/private/Server.php | 15 +++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/OC.php b/lib/OC.php index 725e945219d47..705a4ed653197 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -779,7 +779,11 @@ public static function initForRequest(): void { self::handleAuthHeaders(); // setup the basic server - self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); + if (!isset(self::$server)) { + self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); + } else { + self::$server->reset(); + } self::$server->boot(); self::oneTimeChecks(); diff --git a/lib/private/Server.php b/lib/private/Server.php index 9d6dde25f4220..68de052396352 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -308,10 +308,21 @@ class Server extends ServerContainer { public function __construct( private string $webRoot, - Config $config, + private Config $config, ) { parent::__construct(); + $this->runInitialRegistrations(); + } + + /** + * Empty container and re-run initial registration + */ + public function reset(): void { + parent::__construct(); + $this->runInitialRegistrations(); + } + public function runInitialRegistrations(): void { // To find out if we are running from CLI or not $this->registerParameter('isCLI', \OC::$CLI); $this->registerParameter('serverRoot', \OC::$SERVERROOT); @@ -472,7 +483,7 @@ public function __construct( $this->registerAlias(IConfig::class, AllConfig::class); - $this->registerService(SystemConfig::class, static fn ($c) => new SystemConfig($config)); + $this->registerService(SystemConfig::class, fn ($c) => new SystemConfig($this->config)); $this->registerAlias(IAppConfig::class, \OC\AppConfig::class); $this->registerAlias(IUserConfig::class, UserConfig::class); From 46da1a8ec2223c5c2d702b955e620fbc84b50915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 15 Oct 2026 15:46:03 +0200 Subject: [PATCH 2/4] fix: Reduce the number of service references around MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure to reset exception and error handler, and register shutdown functions through Server instance. For TempManager, rely on destructor instead. This helps reducing the number of service references floating around. There’s still something keeping database connections alive unidentified yet. Signed-off-by: Côme Chilliet --- lib/OC.php | 13 ++++++------- lib/private/Server.php | 16 ++++++++++++++++ lib/private/TempManager.php | 5 +++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/OC.php b/lib/OC.php index 705a4ed653197..81a63f8441bf5 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -782,6 +782,8 @@ public static function initForRequest(): void { if (!isset(self::$server)) { self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); } else { + set_exception_handler(null); + set_error_handler(null); self::$server->reset(); } self::$server->boot(); @@ -829,7 +831,7 @@ public static function initForRequest(): void { } else { set_error_handler([$errorHandler, 'onError']); } - register_shutdown_function([$errorHandler, 'onShutdown']); + self::$server->registerShutdownFunction($errorHandler->onShutdown(...)); set_exception_handler($exceptionHandler); } @@ -928,11 +930,8 @@ public static function initForRequest(): void { $appManager->loadApp('settings'); } - //make sure temporary files are cleaned up - $tmpManager = Server::get(\OCP\ITempManager::class); - register_shutdown_function([$tmpManager, 'clean']); $lockProvider = Server::get(\OCP\Lock\ILockingProvider::class); - register_shutdown_function([$lockProvider, 'releaseAll']); + self::$server->registerShutdownFunction($lockProvider->releaseAll(...)); $host = $request->getInsecureServerHost(); /** @@ -978,11 +977,11 @@ public static function initForRequest(): void { $eventLogger->log('init', 'OC::init', $loaderStart, microtime(true)); $eventLogger->start('runtime', 'Runtime'); $eventLogger->start('request', 'Full request after boot'); - register_shutdown_function(function () use ($eventLogger) { + self::$server->registerShutdownFunction(static function () use ($eventLogger) { $eventLogger->end('request'); }); - register_shutdown_function(function () use ($config) { + self::$server->registerShutdownFunction(static function () use ($config) { $memoryPeak = memory_get_peak_usage(); $debugModeEnabled = $config->getSystemValueBool('debug', false); $memoryLimit = null; diff --git a/lib/private/Server.php b/lib/private/Server.php index 68de052396352..f6187e28e58d4 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -306,18 +306,23 @@ * TODO: hookup all manager classes */ class Server extends ServerContainer { + private array $shutdownFunctions = []; + public function __construct( private string $webRoot, private Config $config, ) { parent::__construct(); $this->runInitialRegistrations(); + + register_shutdown_function($this->shutdown(...)); } /** * Empty container and re-run initial registration */ public function reset(): void { + $this->shutdown(); parent::__construct(); $this->runInitialRegistrations(); } @@ -1156,4 +1161,15 @@ public function setSession(ISession $session): void { public function getWebRoot(): string { return $this->webRoot; } + + public function registerShutdownFunction(callable $function): void { + $this->shutdownFunctions[] = $function; + } + + public function shutdown(): void { + foreach ($this->shutdownFunctions as $shutdownFunction) { + $shutdownFunction(); + } + $this->shutdownFunctions = []; + } } diff --git a/lib/private/TempManager.php b/lib/private/TempManager.php index fa7f891ca9272..c2d0da9a3d027 100644 --- a/lib/private/TempManager.php +++ b/lib/private/TempManager.php @@ -33,6 +33,10 @@ public function __construct( $this->tmpBaseDir = $this->getTempBaseDir(); } + public function __destruct() { + $this->clean(); + } + private function generateTemporaryPath(string $postFix): string { $secureRandom = Server::get(ISecureRandom::class); $absolutePath = $this->tmpBaseDir . '/' . self::TMP_PREFIX . $secureRandom->generate(32, ISecureRandom::CHAR_ALPHANUMERIC); @@ -93,6 +97,7 @@ public function getTemporaryFolder($postFix = ''): string|false { #[\Override] public function clean() { $this->cleanFiles($this->current); + $this->current = []; } /** From 861bae5039fd4d04f9452c66f6176afecc0fd375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Thu, 15 Oct 2026 15:48:24 +0200 Subject: [PATCH 3/4] chore: Force memory_limit for frankenphp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default is too low. Signed-off-by: Côme Chilliet --- Caddyfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Caddyfile b/Caddyfile index 919c0bfd3fc21..f4c0ff26a7391 100644 --- a/Caddyfile +++ b/Caddyfile @@ -8,6 +8,7 @@ frankenphp { max_threads auto max_requests 100 + php_ini memory_limit 512M worker { num 2 @@ -19,7 +20,7 @@ watch core/**/*.php watch 3rdparty/**/*.php watch *.php - + match /index.php/* max_consecutive_failures -1 } @@ -55,7 +56,6 @@ {$SERVER_NAME:localhost} { php_server { - } log { From dd34b4d3344b074348965be48d10da5d5da4ed2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 27 Oct 2026 09:35:58 +0100 Subject: [PATCH 4/4] chore: Fix test and silence psalm false-positive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- lib/OC.php | 1 + tests/lib/TempManagerTest.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/OC.php b/lib/OC.php index 81a63f8441bf5..3404cf982c1ef 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -779,6 +779,7 @@ public static function initForRequest(): void { self::handleAuthHeaders(); // setup the basic server + /** @psalm-suppress RedundantPropertyInitializationCheck The server is first initialized here */ if (!isset(self::$server)) { self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); } else { diff --git a/tests/lib/TempManagerTest.php b/tests/lib/TempManagerTest.php index 06a43930e919f..2d5748217b17d 100644 --- a/tests/lib/TempManagerTest.php +++ b/tests/lib/TempManagerTest.php @@ -173,7 +173,8 @@ public function testGenerateTemporaryPathTraversal(): void { } public function testGetTempBaseDirFromConfig(): void { - $dir = $this->getManager()->getTemporaryFolder(); + $manager1 = $this->getManager(); + $dir = $manager1->getTemporaryFolder(); $config = $this->createMock(IConfig::class); $config->expects($this->once())