Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 45 additions & 11 deletions lib/private/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
use OCP\Exceptions\AppConfigTypeConflictException;
use OCP\Exceptions\AppConfigUnknownKeyException;
use OCP\IAppConfig;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
Expand Down Expand Up @@ -68,11 +70,15 @@ class AppConfig implements IAppConfig {
/** @var ?array<string, string> */
private ?array $appVersionsCache = null;

private ?ICache $localCache;

public function __construct(
protected IDBConnection $connection,
protected LoggerInterface $logger,
protected ICrypto $crypto,
ICacheFactory $cacheFactory,
) {
$this->localCache = $cacheFactory->isLocalCacheAvailable() ? $cacheFactory->createLocal() : null;
}

/**
Expand Down Expand Up @@ -859,6 +865,8 @@ private function setTypedValue(
}
$this->valueTypes[$app][$key] = $type;

$this->clearLocalCache();

return true;
}

Expand Down Expand Up @@ -1118,6 +1126,7 @@ public function deleteKey(string $app, string $key): void {
unset($this->lazyCache[$app][$key]);
unset($this->fastCache[$app][$key]);
unset($this->valueTypes[$app][$key]);
$this->clearLocalCache();
}

/**
Expand Down Expand Up @@ -1147,6 +1156,7 @@ public function deleteApp(string $app): void {
public function clearCache(bool $reload = false): void {
$this->lazyLoaded = $this->fastLoaded = false;
$this->lazyCache = $this->fastCache = $this->valueTypes = [];
$this->clearLocalCache();

if (!$reload) {
return;
Expand Down Expand Up @@ -1217,6 +1227,21 @@ private function loadConfigAll(?string $app = null): void {
$this->loadConfig($app, null);
}

private function getLocalCacheKey(?bool $lazy): string {
return 'app-config' . ($lazy !== null ? '-' . ($lazy === true ? 'lazy' : 'non-lazy') : '');
}

private function clearLocalCache(): void {
if ($this->localCache === null) {
return;
}

$keys = array_map(fn (?bool $lazy) => $this->getLocalCacheKey($lazy), [null, true, false]);
foreach ($keys as $key) {
$this->localCache->remove($key);
}
}

/**
* Load normal config or config set as lazy loaded
*
Expand All @@ -1233,20 +1258,30 @@ private function loadConfig(?string $app = null, ?bool $lazy = false): void {
$this->logger->debug($exception->getMessage(), ['exception' => $exception, 'app' => $app]);
}

$qb = $this->connection->getQueryBuilder();
$qb->from('appconfig');
$cacheKey = $this->getLocalCacheKey($lazy);

// we only need value from lazy when loadConfig does not specify it
$qb->select('appid', 'configkey', 'configvalue', 'type');
$rows = $this->localCache?->get($cacheKey);
if ($rows === null) {
$qb = $this->connection->getQueryBuilder();
$qb->from('appconfig');

if ($lazy !== null) {
$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
} else {
$qb->addSelect('lazy');
// we only need value from lazy when loadConfig does not specify it
$qb->select('appid', 'configkey', 'configvalue', 'type');

if ($lazy !== null) {
$qb->where($qb->expr()->eq('lazy', $qb->createNamedParameter($lazy ? 1 : 0, IQueryBuilder::PARAM_INT)));
} else {
$qb->addSelect('lazy');
}

$result = $qb->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();

// The TTL is low to avoid syncing issues in clustered setups, but it still greatly improves performance when many requests per second are sent.
$this->localCache?->set($cacheKey, $rows, 1);
}

$result = $qb->executeQuery();
$rows = $result->fetchAll();
foreach ($rows as $row) {
// most of the time, 'lazy' is not in the select because its value is already known
if (($row['lazy'] ?? ($lazy ?? 0) ? 1 : 0) === 1) {
Expand All @@ -1256,7 +1291,6 @@ private function loadConfig(?string $app = null, ?bool $lazy = false): void {
}
$this->valueTypes[$row['appid']][$row['configkey']] = (int)($row['type'] ?? 0);
}
$result->closeCursor();
$this->setAsLoaded($lazy);
}

Expand Down
16 changes: 10 additions & 6 deletions lib/private/Memcache/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Factory implements ICacheFactory {
private IProfiler $profiler;

/**
* @param Closure $globalPrefixClosure
* @param Closure(self) $globalPrefixClosure
* @param LoggerInterface $logger
* @param ?class-string<ICache> $localCacheClass
* @param ?class-string<ICache> $distributedCacheClass
Expand Down Expand Up @@ -110,7 +110,7 @@ public function __construct(

private function getGlobalPrefix(): ?string {
if (is_null($this->globalPrefix)) {
$this->globalPrefix = ($this->globalPrefixClosure)();
$this->globalPrefix = ($this->globalPrefixClosure)($this);
}
return $this->globalPrefix;
}
Expand Down Expand Up @@ -175,10 +175,14 @@ public function createDistributed(string $prefix = ''): ICache {
* @param string $prefix
* @return ICache
*/
public function createLocal(string $prefix = ''): ICache {
$globalPrefix = $this->getGlobalPrefix();
if (is_null($globalPrefix)) {
return new ArrayCache($prefix);
public function createLocal(string $prefix = '', bool $disableGlobalPrefix = false): ICache {
if ($disableGlobalPrefix) {
$globalPrefix = '';
} else {
$globalPrefix = $this->getGlobalPrefix();
if (is_null($globalPrefix)) {
return new ArrayCache($prefix);
}
}

assert($this->localCacheClass !== null);
Expand Down
46 changes: 29 additions & 17 deletions lib/private/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OC\App\AppManager;
use OC\App\AppStore\Bundles\BundleFetcher;
use OC\App\AppStore\Fetcher\AppFetcher;
use OC\App\InfoParser;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\AppFramework\Http\Request;
use OC\AppFramework\Http\RequestId;
Expand Down Expand Up @@ -163,7 +164,6 @@
use OCP\Files\Template\ITemplateManager;
use OCP\FilesMetadata\IFilesMetadataManager;
use OCP\FullTextSearch\IFullTextSearchManager;
use OCP\GlobalScale\IConfig;
use OCP\Group\ISubAdmin;
use OCP\Http\Client\IClientService;
use OCP\IAppConfig;
Expand Down Expand Up @@ -602,24 +602,36 @@ public function __construct($webRoot, \OC\Config $config) {
$serverVersion = $c->get(ServerVersion::class);

if ($config->getValue('installed', false) && !(defined('PHPUNIT_RUN') && PHPUNIT_RUN)) {
$logQuery = $config->getValue('log_query');
$prefixClosure = function () use ($logQuery, $serverVersion): ?string {
if (!$logQuery) {
try {
$v = \OCP\Server::get(IAppConfig::class)->getAppInstalledVersions(true);
} catch (\Doctrine\DBAL\Exception $e) {
// Database service probably unavailable
// Probably related to https://github.com/nextcloud/server/issues/37424
return null;
$prefixClosure = function (Factory $factory) use ($serverVersion): string {
// It is impossible to rely on OC_App or AppManager at this point, as they rely on the cache itself.
$appInfoFiles = [];
foreach (\OC::$APPSROOTS as $apps_dir) {
if (!is_readable($apps_dir['path'])) {
continue;
}

$dh = opendir($apps_dir['path']);
if (!is_resource($dh)) {
continue;
}

while (($file = readdir($dh)) !== false) {
if (
$file[0] !== '.'
&& is_dir($apps_dir['path'] . '/' . $file)
&& is_file($apps_dir['path'] . '/' . $file . '/appinfo/info.xml')
) {
$appInfoFiles[$file] = $apps_dir['path'] . '/' . $file . '/appinfo/info.xml';
}
}
} else {
// If the log_query is enabled, we can not get the app versions
// as that does a query, which will be logged and the logging
// depends on redis and here we are back again in the same function.
$v = [
'log_query' => 'enabled',
];
}

// We must disable the global prefix here, in order to compute the global prefix in this closure and avoid infinite recursion.
$prefixlessCache = $factory->createLocal(disableGlobalPrefix: true);

$infoParser = new InfoParser($prefixlessCache);

$v = array_map(static fn ($file): string => (string)$infoParser->parse($file)['version'], $appInfoFiles);
$v['core'] = implode(',', $serverVersion->getVersion());
$version = implode(',', array_keys($v)) . implode(',', $v);
$instanceId = \OC_Util::getInstanceId();
Expand Down
4 changes: 4 additions & 0 deletions tests/lib/AppConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OCP\Exceptions\AppConfigTypeConflictException;
use OCP\Exceptions\AppConfigUnknownKeyException;
use OCP\IAppConfig;
use OCP\ICacheFactory;
use OCP\IDBConnection;
use OCP\Security\ICrypto;
use OCP\Server;
Expand All @@ -29,6 +30,7 @@ class AppConfigTest extends TestCase {
protected IDBConnection $connection;
private LoggerInterface $logger;
private ICrypto $crypto;
private ICacheFactory $cacheFactory;

private array $originalConfig;

Expand Down Expand Up @@ -91,6 +93,7 @@ protected function setUp(): void {
$this->connection = Server::get(IDBConnection::class);
$this->logger = Server::get(LoggerInterface::class);
$this->crypto = Server::get(ICrypto::class);
$this->cacheFactory = Server::get(ICacheFactory::class);

// storing current config and emptying the data table
$sql = $this->connection->getQueryBuilder();
Expand Down Expand Up @@ -181,6 +184,7 @@ private function generateAppConfig(bool $preLoading = true): IAppConfig {
$this->connection,
$this->logger,
$this->crypto,
$this->cacheFactory,
);
$msg = ' generateAppConfig() failed to confirm cache status';

Expand Down
Loading