Compare commits

...

4 Commits

Author SHA1 Message Date
Robin Appelman 05ef7132dc fix: fix oci string length with empty strings
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-01 15:23:45 +02:00
Robin Appelman 8380ae11e7 fix: skip registering mounts if there are no new mount providers
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-01 15:23:42 +02:00
Robin Appelman c5d8643379 fix: improve getMountsForFileId memory usage and performance
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-01 15:21:46 +02:00
Robin Appelman 379239942a fix: reduce memory usage for fetching cached mount into
Signed-off-by: Robin Appelman <robin@icewind.nl>
2025-08-01 15:21:42 +02:00
3 changed files with 84 additions and 62 deletions
@@ -81,12 +81,12 @@ class OCIFunctionBuilder extends FunctionBuilder {
public function octetLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
return new QueryFunction('LENGTHB(' . $quotedName . ')' . $alias);
return new QueryFunction('COALESCE(LENGTHB(' . $quotedName . '), 0)' . $alias);
}
public function charLength($field, $alias = ''): IQueryFunction {
$alias = $alias ? (' AS ' . $this->helper->quoteColumnName($alias)) : '';
$quotedName = $this->helper->quoteColumnName($field);
return new QueryFunction('LENGTH(' . $quotedName . ')' . $alias);
return new QueryFunction('COALESCE(LENGTH(' . $quotedName . '), 0)' . $alias);
}
}
+72 -52
View File
@@ -5,10 +5,12 @@
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OC\Files\Config;
use OC\User\LazyUser;
use OCP\Cache\CappedMemoryCache;
use OCP\DB\IResult;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
@@ -31,11 +33,13 @@ class UserMountCache implements IUserMountCache {
/**
* Cached mount info.
*
* @var CappedMemoryCache<ICachedMountInfo[]>
**/
private CappedMemoryCache $mountsForUsers;
/**
* fileid => internal path mapping for cached mount info.
*
* @var CappedMemoryCache<string>
**/
private CappedMemoryCache $internalPathCache;
@@ -202,6 +206,19 @@ class UserMountCache implements IUserMountCache {
$query->executeStatement();
}
/**
* @param IResult $result
* @return CachedMountInfo[]
*/
private function fetchMountInfo(IResult $result, ?callable $pathCallback = null): array {
$mounts = [];
while ($row = $result->fetch()) {
$mount = $this->dbRowToMountInfo($row, $pathCallback);
$mounts[] = $mount;
}
return $mounts;
}
/**
* @param array $row
* @param (callable(CachedMountInfo): string)|null $pathCallback
@@ -251,19 +268,9 @@ class UserMountCache implements IUserMountCache {
->from('mounts', 'm')
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($userUID)));
$result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
/** @var array<string, ICachedMountInfo> $mounts */
$mounts = [];
foreach ($rows as $row) {
$mount = $this->dbRowToMountInfo($row, [$this, 'getInternalPathForMountInfo']);
if ($mount !== null) {
$mounts[$mount->getKey()] = $mount;
}
}
$this->mountsForUsers[$userUID] = $mounts;
$mounts = $this->fetchMountInfo($query->executeQuery(), [$this, 'getInternalPathForMountInfo']);
$keys = array_map(fn (ICachedMountInfo $mount) => $mount->getKey(), $mounts);
$this->mountsForUsers[$userUID] = array_combine($keys, $mounts);
}
return $this->mountsForUsers[$userUID];
}
@@ -286,8 +293,9 @@ class UserMountCache implements IUserMountCache {
* @return CachedMountInfo[]
*/
public function getMountsForStorageId($numericStorageId, $user = null) {
$mounts = [];
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
$query = $builder->select('id', 'storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($numericStorageId, IQueryBuilder::PARAM_INT)));
@@ -296,11 +304,7 @@ class UserMountCache implements IUserMountCache {
$query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
}
$result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
return $this->fetchMountInfo($query->executeQuery());
}
/**
@@ -314,11 +318,7 @@ class UserMountCache implements IUserMountCache {
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
->where($builder->expr()->eq('root_id', $builder->createNamedParameter($rootFileId, IQueryBuilder::PARAM_INT)));
$result = $query->executeQuery();
$rows = $result->fetchAll();
$result->closeCursor();
return array_filter(array_map([$this, 'dbRowToMountInfo'], $rows));
return $this->fetchMountInfo($query->executeQuery());
}
/**
@@ -341,7 +341,7 @@ class UserMountCache implements IUserMountCache {
$this->cacheInfoCache[$fileId] = [
(int)$row['storage'],
(string)$row['path'],
(int)$row['mimetype']
(int)$row['mimetype'],
];
} else {
throw new NotFoundException('File with id "' . $fileId . '" not found');
@@ -362,34 +362,54 @@ class UserMountCache implements IUserMountCache {
} catch (NotFoundException $e) {
return [];
}
$mountsForStorage = $this->getMountsForStorageId($storageId, $user);
// filter mounts that are from the same storage but not a parent of the file we care about
$filteredMounts = array_filter($mountsForStorage, function (ICachedMountInfo $mount) use ($internalPath, $fileId) {
if ($fileId === $mount->getRootId()) {
return true;
$builder = $this->connection->getQueryBuilder();
$query = $builder->select('id', 'storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
->from('mounts', 'm')
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
->where($builder->expr()->eq('storage_id', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT)))
->andWhere($builder->expr()->orX(
// filter mounts that are from the same storage but not a parent of the file we care about
$builder->expr()->eq('f.fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)),
$builder->expr()->eq('f.path', $builder->createNamedParameter('')),
$builder->expr()->isNull('f.path'),
$builder->expr()->eq(
$builder->func()->concat('f.path', $builder->createNamedParameter('/')),
$builder->func()->substring(
$builder->createNamedParameter($internalPath),
$builder->createNamedParameter(1, IQueryBuilder::PARAM_INT),
$builder->func()->add(
$builder->func()->charLength('f.path'),
$builder->createNamedParameter(1, IQueryBuilder::PARAM_INT),
),
),
),
));
if ($user) {
$query->andWhere($builder->expr()->eq('user_id', $builder->createNamedParameter($user)));
}
$result = $query->executeQuery();
$mounts = [];
while ($row = $result->fetch()) {
$user = $this->userManager->get($row['user_id']);
if ($user) {
$mounts[] = new CachedMountFileInfo(
$user,
(int)$row['storage_id'],
(int)$row['root_id'],
$row['mount_point'],
$row['mount_id'] ? (int)$row['mount_id'] : null,
$row['mount_provider_class'] ?? '',
$row['path'] ?? '',
$internalPath,
);
}
$internalMountPath = $mount->getRootInternalPath();
}
return $internalMountPath === '' || str_starts_with($internalPath, $internalMountPath . '/');
});
$filteredMounts = array_values(array_filter($filteredMounts, function (ICachedMountInfo $mount) {
return $this->userManager->userExists($mount->getUser()->getUID());
}));
return array_map(function (ICachedMountInfo $mount) use ($internalPath) {
return new CachedMountFileInfo(
$mount->getUser(),
$mount->getStorageId(),
$mount->getRootId(),
$mount->getMountPoint(),
$mount->getMountId(),
$mount->getMountProvider(),
$mount->getRootInternalPath(),
$internalPath
);
}, $filteredMounts);
return $mounts;
}
/**
@@ -433,7 +453,7 @@ class UserMountCache implements IUserMountCache {
$mountPoint = $builder->func()->concat(
$builder->func()->concat($slash, 'user_id'),
$slash
$slash,
);
$userIds = array_map(function (IUser $user) {
@@ -445,7 +465,7 @@ class UserMountCache implements IUserMountCache {
->innerJoin('m', 'filecache', 'f',
$builder->expr()->andX(
$builder->expr()->eq('m.storage_id', 'f.storage'),
$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files')))
$builder->expr()->eq('f.path_hash', $builder->createNamedParameter(md5('files'))),
))
->where($builder->expr()->eq('m.mount_point', $mountPoint))
->andWhere($builder->expr()->in('m.user_id', $builder->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)));
+10 -8
View File
@@ -277,10 +277,6 @@ class SetupManager {
private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
$userRoot = '/' . $user->getUID() . '/';
$mounts = $this->mountManager->getAll();
$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
return str_starts_with($mount->getMountPoint(), $userRoot);
});
$allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
return get_class($provider);
}, array_merge(
@@ -289,10 +285,16 @@ class SetupManager {
$this->mountProviderCollection->getRootProviders(),
));
$newProviders = array_diff($allProviders, $previouslySetupProviders);
$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
return !in_array($mount->getMountProvider(), $previouslySetupProviders);
});
$this->registerMounts($user, $mounts, $newProviders);
if (count($newProviders) > 0) {
$mounts = $this->mountManager->getAll();
$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot, $previouslySetupProviders) {
if (!str_starts_with($mount->getMountPoint(), $userRoot)) {
return false;
}
return !in_array($mount->getMountProvider(), $previouslySetupProviders);
});
$this->registerMounts($user, $mounts, $newProviders);
}
$cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
if ($cacheDuration > 0) {