| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/disk_cache/simple/simple_file_tracker.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/files/file.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/synchronization/lock.h" |
| #include "net/disk_cache/simple/simple_histogram_enums.h" |
| #include "net/disk_cache/simple/simple_synchronous_entry.h" |
| |
| namespace disk_cache { |
| |
| SimpleFileTracker::SimpleFileTracker(int file_limit) |
| : file_limit_(file_limit), open_files_(0) {} |
| |
| SimpleFileTracker::~SimpleFileTracker() { |
| DCHECK(lru_.empty()); |
| DCHECK(tracked_files_.empty()); |
| } |
| |
| void SimpleFileTracker::Register(const SimpleSynchronousEntry* owner, |
| SubFile subfile, |
| std::unique_ptr<base::File> file) { |
| DCHECK(file->IsValid()); |
| std::vector<std::unique_ptr<base::File>> files_to_close; |
| |
| { |
| base::AutoLock hold_lock(lock_); |
| |
| // Make sure the list of everything with given hash exists. |
| auto insert_status = tracked_files_.insert( |
| std::make_pair(owner->entry_file_key().entry_hash, |
| std::vector<std::unique_ptr<TrackedFiles>>())); |
| |
| std::vector<std::unique_ptr<TrackedFiles>>& candidates = |
| insert_status.first->second; |
| |
| // See if entry for |owner| already exists, if not append. |
| TrackedFiles* owners_files = nullptr; |
| for (const std::unique_ptr<TrackedFiles>& candidate : candidates) { |
| if (candidate->owner == owner) { |
| owners_files = candidate.get(); |
| break; |
| } |
| } |
| |
| if (!owners_files) { |
| candidates.emplace_back(new TrackedFiles()); |
| owners_files = candidates.back().get(); |
| owners_files->owner = owner; |
| owners_files->key = owner->entry_file_key(); |
| } |
| |
| EnsureInFrontOfLRU(owners_files); |
| |
| int file_index = static_cast<int>(subfile); |
| DCHECK_EQ(TrackedFiles::TF_NO_REGISTRATION, |
| owners_files->state[file_index]); |
| owners_files->files[file_index] = std::move(file); |
| owners_files->state[file_index] = TrackedFiles::TF_REGISTERED; |
| ++open_files_; |
| CloseFilesIfTooManyOpen(&files_to_close); |
| } |
| } |
| |
| SimpleFileTracker::FileHandle SimpleFileTracker::Acquire( |
| const SimpleSynchronousEntry* owner, |
| SubFile subfile) { |
| std::vector<std::unique_ptr<base::File>> files_to_close; |
| |
| { |
| base::AutoLock hold_lock(lock_); |
| TrackedFiles* owners_files = Find(owner); |
| int file_index = static_cast<int>(subfile); |
| |
| DCHECK_EQ(TrackedFiles::TF_REGISTERED, owners_files->state[file_index]); |
| owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED; |
| EnsureInFrontOfLRU(owners_files); |
| |
| // Check to see if we have to reopen the file. That might push us over the |
| // fd limit. CloseFilesIfTooManyOpen will not close anything in |
| // |*owners_files| since it's already in the the TF_ACQUIRED state. |
| if (owners_files->files[file_index] == nullptr) { |
| ReopenFile(owners_files, subfile); |
| CloseFilesIfTooManyOpen(&files_to_close); |
| } |
| |
| return FileHandle(this, owner, subfile, |
| owners_files->files[file_index].get()); |
| } |
| } |
| |
| SimpleFileTracker::TrackedFiles::TrackedFiles() : in_lru(false) { |
| std::fill(state, state + kSimpleEntryTotalFileCount, TF_NO_REGISTRATION); |
| } |
| |
| SimpleFileTracker::TrackedFiles::~TrackedFiles() = default; |
| |
| bool SimpleFileTracker::TrackedFiles::Empty() const { |
| for (State s : state) |
| if (s != TF_NO_REGISTRATION) |
| return false; |
| return true; |
| } |
| |
| bool SimpleFileTracker::TrackedFiles::HasOpenFiles() const { |
| for (const std::unique_ptr<base::File>& file : files) |
| if (file != nullptr) |
| return true; |
| return false; |
| } |
| |
| void SimpleFileTracker::Release(const SimpleSynchronousEntry* owner, |
| SubFile subfile) { |
| std::vector<std::unique_ptr<base::File>> files_to_close; |
| |
| { |
| base::AutoLock hold_lock(lock_); |
| TrackedFiles* owners_files = Find(owner); |
| int file_index = static_cast<int>(subfile); |
| |
| DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED || |
| owners_files->state[file_index] == |
| TrackedFiles::TF_ACQUIRED_PENDING_CLOSE); |
| |
| // Prepare to executed deferred close, if any. |
| if (owners_files->state[file_index] == |
| TrackedFiles::TF_ACQUIRED_PENDING_CLOSE) { |
| files_to_close.push_back(PrepareClose(owners_files, file_index)); |
| } else { |
| owners_files->state[file_index] = TrackedFiles::TF_REGISTERED; |
| } |
| |
| // It's possible that we were over limit and couldn't do much about it |
| // since everything was lent out, so now may be the time to close extra |
| // stuff. |
| CloseFilesIfTooManyOpen(&files_to_close); |
| } |
| } |
| |
| void SimpleFileTracker::Close(const SimpleSynchronousEntry* owner, |
| SubFile subfile) { |
| std::unique_ptr<base::File> file_to_close; |
| |
| { |
| base::AutoLock hold_lock(lock_); |
| TrackedFiles* owners_files = Find(owner); |
| int file_index = static_cast<int>(subfile); |
| |
| DCHECK(owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED || |
| owners_files->state[file_index] == TrackedFiles::TF_REGISTERED); |
| |
| if (owners_files->state[file_index] == TrackedFiles::TF_ACQUIRED) { |
| // The FD is currently acquired, so we can't clean up the TrackedFiles, |
| // just yet; even if this is the last close, so delay the close until it |
| // gets released. |
| owners_files->state[file_index] = TrackedFiles::TF_ACQUIRED_PENDING_CLOSE; |
| } else { |
| file_to_close = PrepareClose(owners_files, file_index); |
| } |
| } |
| } |
| |
| void SimpleFileTracker::Doom(const SimpleSynchronousEntry* owner, |
| EntryFileKey* key) { |
| base::AutoLock hold_lock(lock_); |
| auto iter = tracked_files_.find(key->entry_hash); |
| DCHECK(iter != tracked_files_.end()); |
| |
| uint64_t max_doom_gen = 0; |
| for (const std::unique_ptr<TrackedFiles>& file_with_same_hash : |
| iter->second) { |
| max_doom_gen = |
| std::max(max_doom_gen, file_with_same_hash->key.doom_generation); |
| } |
| |
| // It would take >502 years to doom the same hash enough times (at 10^9 dooms |
| // per second) to wrap the 64 bit counter. Still, if it does wrap around, |
| // there is a security risk since we could confuse different keys. |
| CHECK_NE(max_doom_gen, std::numeric_limits<uint64_t>::max()); |
| uint64_t new_doom_gen = max_doom_gen + 1; |
| |
| // Update external key. |
| key->doom_generation = new_doom_gen; |
| |
| // Update our own. |
| for (const std::unique_ptr<TrackedFiles>& file_with_same_hash : |
| iter->second) { |
| if (file_with_same_hash->owner == owner) |
| file_with_same_hash->key.doom_generation = new_doom_gen; |
| } |
| } |
| |
| bool SimpleFileTracker::IsEmptyForTesting() { |
| base::AutoLock hold_lock(lock_); |
| return tracked_files_.empty() && lru_.empty(); |
| } |
| |
| SimpleFileTracker::TrackedFiles* SimpleFileTracker::Find( |
| const SimpleSynchronousEntry* owner) { |
| auto candidates = tracked_files_.find(owner->entry_file_key().entry_hash); |
| DCHECK(candidates != tracked_files_.end()); |
| for (const auto& candidate : candidates->second) { |
| if (candidate->owner == owner) { |
| return candidate.get(); |
| } |
| } |
| LOG(DFATAL) << "SimpleFileTracker operation on non-found entry"; |
| return nullptr; |
| } |
| |
| std::unique_ptr<base::File> SimpleFileTracker::PrepareClose( |
| TrackedFiles* owners_files, |
| int file_index) { |
| std::unique_ptr<base::File> file_out = |
| std::move(owners_files->files[file_index]); |
| owners_files->state[file_index] = TrackedFiles::TF_NO_REGISTRATION; |
| if (owners_files->Empty()) { |
| auto iter = tracked_files_.find(owners_files->key.entry_hash); |
| for (auto i = iter->second.begin(); i != iter->second.end(); ++i) { |
| if ((*i).get() == owners_files) { |
| if (owners_files->in_lru) |
| lru_.erase(owners_files->position_in_lru); |
| iter->second.erase(i); |
| break; |
| } |
| } |
| if (iter->second.empty()) |
| tracked_files_.erase(iter); |
| } |
| if (file_out != nullptr) |
| --open_files_; |
| return file_out; |
| } |
| |
| void SimpleFileTracker::CloseFilesIfTooManyOpen( |
| std::vector<std::unique_ptr<base::File>>* files_to_close) { |
| auto i = lru_.end(); |
| while (open_files_ > file_limit_ && i != lru_.begin()) { |
| --i; // Point to the actual entry. |
| TrackedFiles* tracked_files = *i; |
| DCHECK(tracked_files->in_lru); |
| for (int j = 0; j < kSimpleEntryTotalFileCount; ++j) { |
| if (tracked_files->state[j] == TrackedFiles::TF_REGISTERED && |
| tracked_files->files[j] != nullptr) { |
| files_to_close->push_back(std::move(tracked_files->files[j])); |
| --open_files_; |
| UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimiterAction", |
| FD_LIMIT_CLOSE_FILE, FD_LIMIT_OP_MAX); |
| } |
| } |
| |
| if (!tracked_files->HasOpenFiles()) { |
| // If there is nothing here that can possibly be closed, remove this from |
| // LRU for now so we don't have to rescan it next time we are here. If the |
| // files get re-opened (in Acquire), it will get added back in. |
| DCHECK_EQ(*tracked_files->position_in_lru, tracked_files); |
| DCHECK(i == tracked_files->position_in_lru); |
| // Note that we're erasing at i, which would make it invalid, so go back |
| // one element ahead to we can decrement from that on next iteration. |
| ++i; |
| lru_.erase(tracked_files->position_in_lru); |
| tracked_files->in_lru = false; |
| } |
| } |
| } |
| |
| void SimpleFileTracker::ReopenFile(TrackedFiles* owners_files, |
| SubFile subfile) { |
| int file_index = static_cast<int>(subfile); |
| DCHECK(owners_files->files[file_index] == nullptr); |
| int flags = base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_WRITE | base::File::FLAG_SHARE_DELETE; |
| base::FilePath file_path = |
| owners_files->owner->GetFilenameForSubfile(subfile); |
| owners_files->files[file_index] = |
| std::make_unique<base::File>(file_path, flags); |
| if (owners_files->files[file_index]->IsValid()) { |
| UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimiterAction", |
| FD_LIMIT_REOPEN_FILE, FD_LIMIT_OP_MAX); |
| |
| ++open_files_; |
| } else { |
| owners_files->files[file_index] = nullptr; |
| UMA_HISTOGRAM_ENUMERATION("SimpleCache.FileDescriptorLimiterAction", |
| FD_LIMIT_FAIL_REOPEN_FILE, FD_LIMIT_OP_MAX); |
| } |
| } |
| |
| void SimpleFileTracker::EnsureInFrontOfLRU(TrackedFiles* owners_files) { |
| if (!owners_files->in_lru) { |
| lru_.push_front(owners_files); |
| owners_files->position_in_lru = lru_.begin(); |
| owners_files->in_lru = true; |
| } else if (owners_files->position_in_lru != lru_.begin()) { |
| lru_.splice(lru_.begin(), lru_, owners_files->position_in_lru); |
| } |
| DCHECK_EQ(*owners_files->position_in_lru, owners_files); |
| } |
| |
| SimpleFileTracker::FileHandle::FileHandle() |
| : file_tracker_(nullptr), entry_(nullptr), file_(nullptr) {} |
| |
| SimpleFileTracker::FileHandle::FileHandle(SimpleFileTracker* file_tracker, |
| const SimpleSynchronousEntry* entry, |
| SimpleFileTracker::SubFile subfile, |
| base::File* file) |
| : file_tracker_(file_tracker), |
| entry_(entry), |
| subfile_(subfile), |
| file_(file) {} |
| |
| SimpleFileTracker::FileHandle::FileHandle(FileHandle&& other) { |
| *this = std::move(other); |
| } |
| |
| SimpleFileTracker::FileHandle::~FileHandle() { |
| if (entry_) |
| file_tracker_->Release(entry_, subfile_); |
| } |
| |
| SimpleFileTracker::FileHandle& SimpleFileTracker::FileHandle::operator=( |
| FileHandle&& other) { |
| file_tracker_ = other.file_tracker_; |
| entry_ = other.entry_; |
| subfile_ = other.subfile_; |
| file_ = other.file_; |
| other.file_tracker_ = nullptr; |
| other.entry_ = nullptr; |
| other.file_ = nullptr; |
| return *this; |
| } |
| |
| base::File* SimpleFileTracker::FileHandle::operator->() const { |
| return file_; |
| } |
| |
| base::File* SimpleFileTracker::FileHandle::get() const { |
| return file_; |
| } |
| |
| bool SimpleFileTracker::FileHandle::IsOK() const { |
| return file_ && file_->IsValid(); |
| } |
| |
| } // namespace disk_cache |