1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/disk_cache/simple/simple_file_tracker.h"
6
7 #include <algorithm>
8 #include <limits>
9 #include <memory>
10 #include <utility>
11
12 #include "base/files/file.h"
13 #include "base/logging.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/synchronization/lock.h"
16 #include "net/disk_cache/disk_cache.h"
17 #include "net/disk_cache/simple/simple_histogram_enums.h"
18 #include "net/disk_cache/simple/simple_synchronous_entry.h"
19
20 namespace disk_cache {
21
22 namespace {
23
RecordFileDescripterLimiterOp(FileDescriptorLimiterOp op)24 void RecordFileDescripterLimiterOp(FileDescriptorLimiterOp op) {
25 UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimiterAction", op,
26 FD_LIMIT_OP_MAX);
27 }
28
29 } // namespace
30
SimpleFileTracker(int file_limit)31 SimpleFileTracker::SimpleFileTracker(int file_limit)
32 : file_limit_(file_limit) {}
33
~SimpleFileTracker()34 SimpleFileTracker::~SimpleFileTracker() {
35 DCHECK(lru_.empty());
36 DCHECK(tracked_files_.empty());
37 }
38
Register(const SimpleSynchronousEntry * owner,SubFile subfile,std::unique_ptr<base::File> file)39 void SimpleFileTracker::Register(const SimpleSynchronousEntry* owner,
40 SubFile subfile,
41 std::unique_ptr<base::File> file) {
42 DCHECK(file->IsValid());
43 std::vector<std::unique_ptr<base::File>> files_to_close;
44
45 {
46 base::AutoLock hold_lock(lock_);
47
48 // Make sure the list of everything with given hash exists.
49 auto insert_status =
50 tracked_files_.emplace(owner->entry_file_key().entry_hash,
51 std::vector<std::unique_ptr<TrackedFiles>>());
52
53 std::vector<std::unique_ptr<TrackedFiles>>& candidates =
54 insert_status.first->second;
55
56 // See if entry for |owner| already exists, if not append.
57 TrackedFiles* owners_files = nullptr;
58 for (const std::unique_ptr<TrackedFiles>& candidate : candidates) {
59 if (candidate->owner == owner) {
60 owners_files = candidate.get();
61 break;
62 }
63 }
64
65 if (!owners_files) {
66 candidates.emplace_back(std::make_unique<TrackedFiles>());
67 owners_files = candidates.back().get();
68 owners_files->owner = owner;
69 owners_files->key = owner->entry_file_key();
70 }
71
72 EnsureInFrontOfLRU(owners_files);
73
74 int file_index = static_cast<int>(subfile);
75 DCHECK_EQ(TrackedFiles::TF_NO_REGISTRATION,
76 owners_files->state[file_index]);
77 owners_files->files[file_index] = std::move(file);
78 owners_files->state[file_index] = TrackedFiles::TF_REGISTERED;
79 ++open_files_;
80 CloseFilesIfTooManyOpen(&files_to_close);
81 }
82 }
83
Acquire(BackendFileOperations * file_operations,const SimpleSynchronousEntry * owner,SubFile subfile)84 SimpleFileTracker::FileHandle SimpleFileTracker::Acquire(
85 BackendFileOperations* file_operations,
86 const SimpleSynchronousEntry* owner,
87 SubFile subfile) {
88 std::vector<std::unique_ptr<base::File>> files_to_close;
89
90 {
91 base::AutoLock hold_lock(lock_);
92 TrackedFiles* owners_files = Find(owner);
93 int file_index = static_cast<int>(subfile);
94
95 DCHECK_EQ(TrackedFiles::TF_REGISTERED, owners_files->state[file_index]);
96 owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED;
97 EnsureInFrontOfLRU(owners_files);
98
99 // Check to see if we have to reopen the file. That might push us over the
100 // fd limit. CloseFilesIfTooManyOpen will not close anything in
101 // |*owners_files| since it's already in the the TF_ACQUIRED state.
102 if (owners_files->files[file_index] == nullptr) {
103 ReopenFile(file_operations, owners_files, subfile);
104 CloseFilesIfTooManyOpen(&files_to_close);
105 }
106
107 return FileHandle(this, owner, subfile,
108 owners_files->files[file_index].get());
109 }
110 }
111
TrackedFiles()112 SimpleFileTracker::TrackedFiles::TrackedFiles() {
113 std::fill(state, state + kSimpleEntryTotalFileCount, TF_NO_REGISTRATION);
114 }
115
116 SimpleFileTracker::TrackedFiles::~TrackedFiles() = default;
117
Empty() const118 bool SimpleFileTracker::TrackedFiles::Empty() const {
119 for (State s : state)
120 if (s != TF_NO_REGISTRATION)
121 return false;
122 return true;
123 }
124
HasOpenFiles() const125 bool SimpleFileTracker::TrackedFiles::HasOpenFiles() const {
126 for (const std::unique_ptr<base::File>& file : files)
127 if (file != nullptr)
128 return true;
129 return false;
130 }
131
Release(const SimpleSynchronousEntry * owner,SubFile subfile)132 void SimpleFileTracker::Release(const SimpleSynchronousEntry* owner,
133 SubFile subfile) {
134 std::vector<std::unique_ptr<base::File>> files_to_close;
135
136 {
137 base::AutoLock hold_lock(lock_);
138 TrackedFiles* owners_files = Find(owner);
139 int file_index = static_cast<int>(subfile);
140
141 DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED ||
142 owners_files->state[file_index] ==
143 TrackedFiles::TF_ACQUIRED_PENDING_CLOSE);
144
145 // Prepare to executed deferred close, if any.
146 if (owners_files->state[file_index] ==
147 TrackedFiles::TF_ACQUIRED_PENDING_CLOSE) {
148 files_to_close.push_back(PrepareClose(owners_files, file_index));
149 } else {
150 owners_files->state[file_index] = TrackedFiles::TF_REGISTERED;
151 }
152
153 // It's possible that we were over limit and couldn't do much about it
154 // since everything was lent out, so now may be the time to close extra
155 // stuff.
156 CloseFilesIfTooManyOpen(&files_to_close);
157 }
158 }
159
Close(const SimpleSynchronousEntry * owner,SubFile subfile)160 void SimpleFileTracker::Close(const SimpleSynchronousEntry* owner,
161 SubFile subfile) {
162 std::unique_ptr<base::File> file_to_close;
163
164 {
165 base::AutoLock hold_lock(lock_);
166 TrackedFiles* owners_files = Find(owner);
167 int file_index = static_cast<int>(subfile);
168
169 DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED ||
170 owners_files->state[file_index] == TrackedFiles::TF_REGISTERED);
171
172 if (owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED) {
173 // The FD is currently acquired, so we can't clean up the TrackedFiles,
174 // just yet; even if this is the last close, so delay the close until it
175 // gets released.
176 owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED_PENDING_CLOSE;
177 } else {
178 file_to_close = PrepareClose(owners_files, file_index);
179 }
180 }
181 }
182
Doom(const SimpleSynchronousEntry * owner,EntryFileKey * key)183 void SimpleFileTracker::Doom(const SimpleSynchronousEntry* owner,
184 EntryFileKey* key) {
185 base::AutoLock hold_lock(lock_);
186 auto iter = tracked_files_.find(key->entry_hash);
187 DCHECK(iter != tracked_files_.end());
188
189 uint64_t max_doom_gen = 0;
190 for (const std::unique_ptr<TrackedFiles>& file_with_same_hash :
191 iter->second) {
192 max_doom_gen =
193 std::max(max_doom_gen, file_with_same_hash->key.doom_generation);
194 }
195
196 // It would take >502 years to doom the same hash enough times (at 10^9 dooms
197 // per second) to wrap the 64 bit counter. Still, if it does wrap around,
198 // there is a security risk since we could confuse different keys.
199 CHECK_NE(max_doom_gen, std::numeric_limits<uint64_t>::max());
200 uint64_t new_doom_gen = max_doom_gen + 1;
201
202 // Update external key.
203 key->doom_generation = new_doom_gen;
204
205 // Update our own.
206 for (const std::unique_ptr<TrackedFiles>& file_with_same_hash :
207 iter->second) {
208 if (file_with_same_hash->owner == owner)
209 file_with_same_hash->key.doom_generation = new_doom_gen;
210 }
211 }
212
IsEmptyForTesting()213 bool SimpleFileTracker::IsEmptyForTesting() {
214 base::AutoLock hold_lock(lock_);
215 return tracked_files_.empty() && lru_.empty();
216 }
217
Find(const SimpleSynchronousEntry * owner)218 SimpleFileTracker::TrackedFiles* SimpleFileTracker::Find(
219 const SimpleSynchronousEntry* owner) {
220 auto candidates = tracked_files_.find(owner->entry_file_key().entry_hash);
221 DCHECK(candidates != tracked_files_.end());
222 for (const auto& candidate : candidates->second) {
223 if (candidate->owner == owner) {
224 return candidate.get();
225 }
226 }
227 LOG(DFATAL) << "SimpleFileTracker operation on non-found entry";
228 return nullptr;
229 }
230
PrepareClose(TrackedFiles * owners_files,int file_index)231 std::unique_ptr<base::File> SimpleFileTracker::PrepareClose(
232 TrackedFiles* owners_files,
233 int file_index) {
234 std::unique_ptr<base::File> file_out =
235 std::move(owners_files->files[file_index]);
236 owners_files->state[file_index] = TrackedFiles::TF_NO_REGISTRATION;
237 if (owners_files->Empty()) {
238 auto iter = tracked_files_.find(owners_files->key.entry_hash);
239 for (auto i = iter->second.begin(); i != iter->second.end(); ++i) {
240 if ((*i).get() == owners_files) {
241 if (owners_files->in_lru)
242 lru_.erase(owners_files->position_in_lru);
243 iter->second.erase(i);
244 break;
245 }
246 }
247 if (iter->second.empty())
248 tracked_files_.erase(iter);
249 }
250 if (file_out != nullptr)
251 --open_files_;
252 return file_out;
253 }
254
CloseFilesIfTooManyOpen(std::vector<std::unique_ptr<base::File>> * files_to_close)255 void SimpleFileTracker::CloseFilesIfTooManyOpen(
256 std::vector<std::unique_ptr<base::File>>* files_to_close) {
257 auto i = lru_.end();
258 while (open_files_ > file_limit_ && i != lru_.begin()) {
259 --i; // Point to the actual entry.
260 TrackedFiles* tracked_files = *i;
261 DCHECK(tracked_files->in_lru);
262 for (int j = 0; j < kSimpleEntryTotalFileCount; ++j) {
263 if (tracked_files->state[j] == TrackedFiles::TF_REGISTERED &&
264 tracked_files->files[j] != nullptr) {
265 files_to_close->push_back(std::move(tracked_files->files[j]));
266 --open_files_;
267 RecordFileDescripterLimiterOp(FD_LIMIT_CLOSE_FILE);
268 }
269 }
270
271 if (!tracked_files->HasOpenFiles()) {
272 // If there is nothing here that can possibly be closed, remove this from
273 // LRU for now so we don't have to rescan it next time we are here. If the
274 // files get re-opened (in Acquire), it will get added back in.
275 DCHECK_EQ(*tracked_files->position_in_lru, tracked_files);
276 DCHECK(i == tracked_files->position_in_lru);
277 // Note that we're erasing at i, which would make it invalid, so go back
278 // one element ahead to we can decrement from that on next iteration.
279 ++i;
280 lru_.erase(tracked_files->position_in_lru);
281 tracked_files->in_lru = false;
282 }
283 }
284 }
285
ReopenFile(BackendFileOperations * file_operations,TrackedFiles * owners_files,SubFile subfile)286 void SimpleFileTracker::ReopenFile(BackendFileOperations* file_operations,
287 TrackedFiles* owners_files,
288 SubFile subfile) {
289 int file_index = static_cast<int>(subfile);
290 DCHECK(owners_files->files[file_index] == nullptr);
291 int flags = base::File::FLAG_OPEN | base::File::FLAG_READ |
292 base::File::FLAG_WRITE | base::File::FLAG_WIN_SHARE_DELETE;
293 base::FilePath file_path =
294 owners_files->owner->GetFilenameForSubfile(subfile);
295 owners_files->files[file_index] =
296 std::make_unique<base::File>(file_operations->OpenFile(file_path, flags));
297 if (owners_files->files[file_index]->IsValid()) {
298 RecordFileDescripterLimiterOp(FD_LIMIT_REOPEN_FILE);
299
300 ++open_files_;
301 } else {
302 owners_files->files[file_index] = nullptr;
303 RecordFileDescripterLimiterOp(FD_LIMIT_FAIL_REOPEN_FILE);
304 }
305 }
306
EnsureInFrontOfLRU(TrackedFiles * owners_files)307 void SimpleFileTracker::EnsureInFrontOfLRU(TrackedFiles* owners_files) {
308 if (!owners_files->in_lru) {
309 lru_.push_front(owners_files);
310 owners_files->position_in_lru = lru_.begin();
311 owners_files->in_lru = true;
312 } else if (owners_files->position_in_lru != lru_.begin()) {
313 lru_.splice(lru_.begin(), lru_, owners_files->position_in_lru);
314 }
315 DCHECK_EQ(*owners_files->position_in_lru, owners_files);
316 }
317
318 SimpleFileTracker::FileHandle::FileHandle() = default;
319
FileHandle(SimpleFileTracker * file_tracker,const SimpleSynchronousEntry * entry,SimpleFileTracker::SubFile subfile,base::File * file)320 SimpleFileTracker::FileHandle::FileHandle(SimpleFileTracker* file_tracker,
321 const SimpleSynchronousEntry* entry,
322 SimpleFileTracker::SubFile subfile,
323 base::File* file)
324 : file_tracker_(file_tracker),
325 entry_(entry),
326 subfile_(subfile),
327 file_(file) {}
328
FileHandle(FileHandle && other)329 SimpleFileTracker::FileHandle::FileHandle(FileHandle&& other) {
330 *this = std::move(other);
331 }
332
~FileHandle()333 SimpleFileTracker::FileHandle::~FileHandle() {
334 file_ = nullptr;
335 if (entry_) {
336 file_tracker_->Release(entry_.ExtractAsDangling(), subfile_);
337 }
338 }
339
operator =(FileHandle && other)340 SimpleFileTracker::FileHandle& SimpleFileTracker::FileHandle::operator=(
341 FileHandle&& other) {
342 file_tracker_ = other.file_tracker_;
343 entry_ = other.entry_;
344 subfile_ = other.subfile_;
345 file_ = other.file_;
346 other.file_tracker_ = nullptr;
347 other.entry_ = nullptr;
348 other.file_ = nullptr;
349 return *this;
350 }
351
operator ->() const352 base::File* SimpleFileTracker::FileHandle::operator->() const {
353 return file_;
354 }
355
get() const356 base::File* SimpleFileTracker::FileHandle::get() const {
357 return file_;
358 }
359
IsOK() const360 bool SimpleFileTracker::FileHandle::IsOK() const {
361 return file_ && file_->IsValid();
362 }
363
364 } // namespace disk_cache
365