diff options
| author | 2023-06-04 18:44:50 -0400 | |
|---|---|---|
| committer | 2023-06-13 10:37:34 -0400 | |
| commit | f25236a4d61351907cab4009b734ed01a0fd3dc8 (patch) | |
| tree | 803047ac9b23a6edeea6ed16bd37d8ca59bb2ba7 /src | |
| parent | Merge pull request #10746 from bunnei/update-android-settings (diff) | |
| download | yuzu-f25236a4d61351907cab4009b734ed01a0fd3dc8.tar.gz yuzu-f25236a4d61351907cab4009b734ed01a0fd3dc8.tar.xz yuzu-f25236a4d61351907cab4009b734ed01a0fd3dc8.zip | |
vfs_real: add file LRU cache for open file limits
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/file_sys/vfs_real.cpp | 177 | ||||
| -rw-r--r-- | src/core/file_sys/vfs_real.h | 29 |
2 files changed, 106 insertions, 100 deletions
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index cc0076238..a63629b80 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp | |||
| @@ -25,6 +25,8 @@ namespace FS = Common::FS; | |||
| 25 | 25 | ||
| 26 | namespace { | 26 | namespace { |
| 27 | 27 | ||
| 28 | constexpr size_t MaxOpenFiles = 512; | ||
| 29 | |||
| 28 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { | 30 | constexpr FS::FileAccessMode ModeFlagsToFileAccessMode(Mode mode) { |
| 29 | switch (mode) { | 31 | switch (mode) { |
| 30 | case Mode::Read: | 32 | case Mode::Read: |
| @@ -73,24 +75,17 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { | |||
| 73 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { | 75 | VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { |
| 74 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 76 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 75 | 77 | ||
| 76 | if (const auto weak_iter = cache.find(path); weak_iter != cache.cend()) { | 78 | this->EvictSingleReference(); |
| 77 | const auto& weak = weak_iter->second; | ||
| 78 | |||
| 79 | if (!weak.expired()) { | ||
| 80 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, weak.lock(), path, perms)); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | 79 | ||
| 84 | auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); | 80 | auto backing = FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); |
| 85 | |||
| 86 | if (!backing) { | 81 | if (!backing) { |
| 87 | return nullptr; | 82 | return nullptr; |
| 88 | } | 83 | } |
| 89 | 84 | ||
| 90 | cache.insert_or_assign(path, std::move(backing)); | 85 | num_open_files++; |
| 91 | 86 | auto reference = std::make_unique<FileReference>(std::move(backing)); | |
| 92 | // Cannot use make_shared as RealVfsFile constructor is private | 87 | this->InsertReferenceIntoList(*reference); |
| 93 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, backing, path, perms)); | 88 | return std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms)); |
| 94 | } | 89 | } |
| 95 | 90 | ||
| 96 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { | 91 | VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { |
| @@ -123,51 +118,19 @@ VirtualFile RealVfsFilesystem::CopyFile(std::string_view old_path_, std::string_ | |||
| 123 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { | 118 | VirtualFile RealVfsFilesystem::MoveFile(std::string_view old_path_, std::string_view new_path_) { |
| 124 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); | 119 | const auto old_path = FS::SanitizePath(old_path_, FS::DirectorySeparator::PlatformDefault); |
| 125 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); | 120 | const auto new_path = FS::SanitizePath(new_path_, FS::DirectorySeparator::PlatformDefault); |
| 126 | const auto cached_file_iter = cache.find(old_path); | 121 | if (!FS::RenameFile(old_path, new_path)) { |
| 127 | |||
| 128 | if (cached_file_iter != cache.cend()) { | ||
| 129 | auto file = cached_file_iter->second.lock(); | ||
| 130 | |||
| 131 | if (!cached_file_iter->second.expired()) { | ||
| 132 | file->Close(); | ||
| 133 | } | ||
| 134 | |||
| 135 | if (!FS::RenameFile(old_path, new_path)) { | ||
| 136 | return nullptr; | ||
| 137 | } | ||
| 138 | |||
| 139 | cache.erase(old_path); | ||
| 140 | file->Open(new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | ||
| 141 | if (file->IsOpen()) { | ||
| 142 | cache.insert_or_assign(new_path, std::move(file)); | ||
| 143 | } else { | ||
| 144 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", new_path); | ||
| 145 | } | ||
| 146 | } else { | ||
| 147 | ASSERT(false); | ||
| 148 | return nullptr; | 122 | return nullptr; |
| 149 | } | 123 | } |
| 150 | |||
| 151 | return OpenFile(new_path, Mode::ReadWrite); | 124 | return OpenFile(new_path, Mode::ReadWrite); |
| 152 | } | 125 | } |
| 153 | 126 | ||
| 154 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { | 127 | bool RealVfsFilesystem::DeleteFile(std::string_view path_) { |
| 155 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 128 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 156 | const auto cached_iter = cache.find(path); | ||
| 157 | |||
| 158 | if (cached_iter != cache.cend()) { | ||
| 159 | if (!cached_iter->second.expired()) { | ||
| 160 | cached_iter->second.lock()->Close(); | ||
| 161 | } | ||
| 162 | cache.erase(path); | ||
| 163 | } | ||
| 164 | |||
| 165 | return FS::RemoveFile(path); | 129 | return FS::RemoveFile(path); |
| 166 | } | 130 | } |
| 167 | 131 | ||
| 168 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { | 132 | VirtualDir RealVfsFilesystem::OpenDirectory(std::string_view path_, Mode perms) { |
| 169 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 133 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 170 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 171 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 134 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 172 | } | 135 | } |
| 173 | 136 | ||
| @@ -176,7 +139,6 @@ VirtualDir RealVfsFilesystem::CreateDirectory(std::string_view path_, Mode perms | |||
| 176 | if (!FS::CreateDirs(path)) { | 139 | if (!FS::CreateDirs(path)) { |
| 177 | return nullptr; | 140 | return nullptr; |
| 178 | } | 141 | } |
| 179 | // Cannot use make_shared as RealVfsDirectory constructor is private | ||
| 180 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); | 142 | return std::shared_ptr<RealVfsDirectory>(new RealVfsDirectory(*this, path, perms)); |
| 181 | } | 143 | } |
| 182 | 144 | ||
| @@ -194,73 +156,102 @@ VirtualDir RealVfsFilesystem::MoveDirectory(std::string_view old_path_, | |||
| 194 | if (!FS::RenameDir(old_path, new_path)) { | 156 | if (!FS::RenameDir(old_path, new_path)) { |
| 195 | return nullptr; | 157 | return nullptr; |
| 196 | } | 158 | } |
| 159 | return OpenDirectory(new_path, Mode::ReadWrite); | ||
| 160 | } | ||
| 197 | 161 | ||
| 198 | for (auto& kv : cache) { | 162 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { |
| 199 | // If the path in the cache doesn't start with old_path, then bail on this file. | 163 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); |
| 200 | if (kv.first.rfind(old_path, 0) != 0) { | 164 | return FS::RemoveDirRecursively(path); |
| 201 | continue; | 165 | } |
| 202 | } | ||
| 203 | 166 | ||
| 204 | const auto file_old_path = | 167 | void RealVfsFilesystem::RefreshReference(const std::string& path, Mode perms, |
| 205 | FS::SanitizePath(kv.first, FS::DirectorySeparator::PlatformDefault); | 168 | FileReference& reference) { |
| 206 | auto file_new_path = FS::SanitizePath(new_path + '/' + kv.first.substr(old_path.size()), | 169 | // Temporarily remove from list. |
| 207 | FS::DirectorySeparator::PlatformDefault); | 170 | this->RemoveReferenceFromList(reference); |
| 208 | const auto& cached = cache[file_old_path]; | ||
| 209 | 171 | ||
| 210 | if (cached.expired()) { | 172 | // Restore file if needed. |
| 211 | continue; | 173 | if (!reference.file) { |
| 212 | } | 174 | this->EvictSingleReference(); |
| 213 | 175 | ||
| 214 | auto file = cached.lock(); | 176 | reference.file = |
| 215 | cache.erase(file_old_path); | 177 | FS::FileOpen(path, ModeFlagsToFileAccessMode(perms), FS::FileType::BinaryFile); |
| 216 | file->Open(file_new_path, FS::FileAccessMode::Read, FS::FileType::BinaryFile); | 178 | if (reference.file) { |
| 217 | if (file->IsOpen()) { | 179 | num_open_files++; |
| 218 | cache.insert_or_assign(std::move(file_new_path), std::move(file)); | ||
| 219 | } else { | ||
| 220 | LOG_ERROR(Service_FS, "Failed to open path {} in order to re-cache it", file_new_path); | ||
| 221 | } | 180 | } |
| 222 | } | 181 | } |
| 223 | 182 | ||
| 224 | return OpenDirectory(new_path, Mode::ReadWrite); | 183 | // Reinsert into list. |
| 184 | this->InsertReferenceIntoList(reference); | ||
| 225 | } | 185 | } |
| 226 | 186 | ||
| 227 | bool RealVfsFilesystem::DeleteDirectory(std::string_view path_) { | 187 | void RealVfsFilesystem::DropReference(std::unique_ptr<FileReference>&& reference) { |
| 228 | const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); | 188 | // Remove from list. |
| 189 | this->RemoveReferenceFromList(*reference); | ||
| 229 | 190 | ||
| 230 | for (auto& kv : cache) { | 191 | // Close the file. |
| 231 | // If the path in the cache doesn't start with path, then bail on this file. | 192 | if (reference->file) { |
| 232 | if (kv.first.rfind(path, 0) != 0) { | 193 | reference->file.reset(); |
| 233 | continue; | 194 | num_open_files--; |
| 234 | } | 195 | } |
| 196 | } | ||
| 235 | 197 | ||
| 236 | const auto& entry = cache[kv.first]; | 198 | void RealVfsFilesystem::EvictSingleReference() { |
| 237 | if (!entry.expired()) { | 199 | if (num_open_files < MaxOpenFiles || open_references.empty()) { |
| 238 | entry.lock()->Close(); | 200 | return; |
| 239 | } | 201 | } |
| 202 | |||
| 203 | // Get and remove from list. | ||
| 204 | auto& reference = open_references.back(); | ||
| 205 | this->RemoveReferenceFromList(reference); | ||
| 240 | 206 | ||
| 241 | cache.erase(kv.first); | 207 | // Close the file. |
| 208 | if (reference.file) { | ||
| 209 | reference.file.reset(); | ||
| 210 | num_open_files--; | ||
| 242 | } | 211 | } |
| 243 | 212 | ||
| 244 | return FS::RemoveDirRecursively(path); | 213 | // Reinsert into closed list. |
| 214 | this->InsertReferenceIntoList(reference); | ||
| 215 | } | ||
| 216 | |||
| 217 | void RealVfsFilesystem::InsertReferenceIntoList(FileReference& reference) { | ||
| 218 | if (reference.file) { | ||
| 219 | open_references.push_front(reference); | ||
| 220 | } else { | ||
| 221 | closed_references.push_front(reference); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) { | ||
| 226 | if (reference.file) { | ||
| 227 | open_references.erase(open_references.iterator_to(reference)); | ||
| 228 | } else { | ||
| 229 | closed_references.erase(closed_references.iterator_to(reference)); | ||
| 230 | } | ||
| 245 | } | 231 | } |
| 246 | 232 | ||
| 247 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FS::IOFile> backing_, | 233 | RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr<FileReference> reference_, |
| 248 | const std::string& path_, Mode perms_) | 234 | const std::string& path_, Mode perms_) |
| 249 | : base(base_), backing(std::move(backing_)), path(path_), parent_path(FS::GetParentPath(path_)), | 235 | : base(base_), reference(std::move(reference_)), path(path_), |
| 250 | path_components(FS::SplitPathComponents(path_)), perms(perms_) {} | 236 | parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), |
| 237 | perms(perms_) {} | ||
| 251 | 238 | ||
| 252 | RealVfsFile::~RealVfsFile() = default; | 239 | RealVfsFile::~RealVfsFile() { |
| 240 | base.DropReference(std::move(reference)); | ||
| 241 | } | ||
| 253 | 242 | ||
| 254 | std::string RealVfsFile::GetName() const { | 243 | std::string RealVfsFile::GetName() const { |
| 255 | return path_components.back(); | 244 | return path_components.back(); |
| 256 | } | 245 | } |
| 257 | 246 | ||
| 258 | std::size_t RealVfsFile::GetSize() const { | 247 | std::size_t RealVfsFile::GetSize() const { |
| 259 | return backing->GetSize(); | 248 | base.RefreshReference(path, perms, *reference); |
| 249 | return reference->file ? reference->file->GetSize() : 0; | ||
| 260 | } | 250 | } |
| 261 | 251 | ||
| 262 | bool RealVfsFile::Resize(std::size_t new_size) { | 252 | bool RealVfsFile::Resize(std::size_t new_size) { |
| 263 | return backing->SetSize(new_size); | 253 | base.RefreshReference(path, perms, *reference); |
| 254 | return reference->file ? reference->file->SetSize(new_size) : false; | ||
| 264 | } | 255 | } |
| 265 | 256 | ||
| 266 | VirtualDir RealVfsFile::GetContainingDirectory() const { | 257 | VirtualDir RealVfsFile::GetContainingDirectory() const { |
| @@ -276,27 +267,25 @@ bool RealVfsFile::IsReadable() const { | |||
| 276 | } | 267 | } |
| 277 | 268 | ||
| 278 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { | 269 | std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { |
| 279 | if (!backing->Seek(static_cast<s64>(offset))) { | 270 | base.RefreshReference(path, perms, *reference); |
| 271 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 280 | return 0; | 272 | return 0; |
| 281 | } | 273 | } |
| 282 | return backing->ReadSpan(std::span{data, length}); | 274 | return reference->file->ReadSpan(std::span{data, length}); |
| 283 | } | 275 | } |
| 284 | 276 | ||
| 285 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { | 277 | std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { |
| 286 | if (!backing->Seek(static_cast<s64>(offset))) { | 278 | base.RefreshReference(path, perms, *reference); |
| 279 | if (!reference->file || !reference->file->Seek(static_cast<s64>(offset))) { | ||
| 287 | return 0; | 280 | return 0; |
| 288 | } | 281 | } |
| 289 | return backing->WriteSpan(std::span{data, length}); | 282 | return reference->file->WriteSpan(std::span{data, length}); |
| 290 | } | 283 | } |
| 291 | 284 | ||
| 292 | bool RealVfsFile::Rename(std::string_view name) { | 285 | bool RealVfsFile::Rename(std::string_view name) { |
| 293 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; | 286 | return base.MoveFile(path, parent_path + '/' + std::string(name)) != nullptr; |
| 294 | } | 287 | } |
| 295 | 288 | ||
| 296 | void RealVfsFile::Close() { | ||
| 297 | backing->Close(); | ||
| 298 | } | ||
| 299 | |||
| 300 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if | 289 | // TODO(DarkLordZach): MSVC would not let me combine the following two functions using 'if |
| 301 | // constexpr' because there is a compile error in the branch not used. | 290 | // constexpr' because there is a compile error in the branch not used. |
| 302 | 291 | ||
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index b92c84316..f29c69fbd 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <string_view> | 6 | #include <string_view> |
| 7 | #include <boost/container/flat_map.hpp> | 7 | #include "common/intrusive_list.h" |
| 8 | #include "core/file_sys/mode.h" | 8 | #include "core/file_sys/mode.h" |
| 9 | #include "core/file_sys/vfs.h" | 9 | #include "core/file_sys/vfs.h" |
| 10 | 10 | ||
| @@ -14,6 +14,12 @@ class IOFile; | |||
| 14 | 14 | ||
| 15 | namespace FileSys { | 15 | namespace FileSys { |
| 16 | 16 | ||
| 17 | struct FileReference : public Common::IntrusiveListBaseNode<FileReference> { | ||
| 18 | FileReference(std::shared_ptr<Common::FS::IOFile>&& f) : file(f) {} | ||
| 19 | std::shared_ptr<Common::FS::IOFile> file; | ||
| 20 | }; | ||
| 21 | |||
| 22 | class RealVfsFile; | ||
| 17 | class RealVfsFilesystem : public VfsFilesystem { | 23 | class RealVfsFilesystem : public VfsFilesystem { |
| 18 | public: | 24 | public: |
| 19 | RealVfsFilesystem(); | 25 | RealVfsFilesystem(); |
| @@ -35,7 +41,20 @@ public: | |||
| 35 | bool DeleteDirectory(std::string_view path) override; | 41 | bool DeleteDirectory(std::string_view path) override; |
| 36 | 42 | ||
| 37 | private: | 43 | private: |
| 38 | boost::container::flat_map<std::string, std::weak_ptr<Common::FS::IOFile>> cache; | 44 | using ReferenceListType = Common::IntrusiveListBaseTraits<FileReference>::ListType; |
| 45 | ReferenceListType open_references; | ||
| 46 | ReferenceListType closed_references; | ||
| 47 | size_t num_open_files{}; | ||
| 48 | |||
| 49 | private: | ||
| 50 | friend class RealVfsFile; | ||
| 51 | void RefreshReference(const std::string& path, Mode perms, FileReference& reference); | ||
| 52 | void DropReference(std::unique_ptr<FileReference>&& reference); | ||
| 53 | void EvictSingleReference(); | ||
| 54 | |||
| 55 | private: | ||
| 56 | void InsertReferenceIntoList(FileReference& reference); | ||
| 57 | void RemoveReferenceFromList(FileReference& reference); | ||
| 39 | }; | 58 | }; |
| 40 | 59 | ||
| 41 | // An implementation of VfsFile that represents a file on the user's computer. | 60 | // An implementation of VfsFile that represents a file on the user's computer. |
| @@ -57,13 +76,11 @@ public: | |||
| 57 | bool Rename(std::string_view name) override; | 76 | bool Rename(std::string_view name) override; |
| 58 | 77 | ||
| 59 | private: | 78 | private: |
| 60 | RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<Common::FS::IOFile> backing, | 79 | RealVfsFile(RealVfsFilesystem& base, std::unique_ptr<FileReference> reference, |
| 61 | const std::string& path, Mode perms = Mode::Read); | 80 | const std::string& path, Mode perms = Mode::Read); |
| 62 | 81 | ||
| 63 | void Close(); | ||
| 64 | |||
| 65 | RealVfsFilesystem& base; | 82 | RealVfsFilesystem& base; |
| 66 | std::shared_ptr<Common::FS::IOFile> backing; | 83 | std::unique_ptr<FileReference> reference; |
| 67 | std::string path; | 84 | std::string path; |
| 68 | std::string parent_path; | 85 | std::string parent_path; |
| 69 | std::vector<std::string> path_components; | 86 | std::vector<std::string> path_components; |