| // 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 |