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