xref: /aosp_15_r20/external/cronet/net/disk_cache/simple/simple_file_tracker.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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