diff options
author | Wyatt Hepler <hepler@google.com> | 2020-03-11 18:24:43 -0700 |
---|---|---|
committer | CQ Bot Account <commit-bot@chromium.org> | 2020-03-17 08:44:16 +0000 |
commit | 7ded6da8bc91931647b367b1489217871b872976 (patch) | |
tree | 44958c7897d01694fb59d166013e0411f540e0fc /pw_kvs/entry_cache.cc | |
parent | 0054a9be7053688b4001319ca7e9bce9340f9e9f (diff) | |
download | pigweed-7ded6da8bc91931647b367b1489217871b872976.tar.gz |
pw_kvs: EntryCache class
- Allocate space for addresses in the derived KeyValueStore class. This
allows having multiple KeyValueStores with different redundancies,
without any wasted memory.
- Addresses are moved to a contiguous array instead of a Vector,
removing the kMaxEntries * uint32_t bytes of Vector overhead.
- Create the EntryCache and EntryMetadata abstractions. These bring
together KeyDescriptors and their addresses.
- Move functions for finding and updating KeyDescriptors to the
EntryCache and EntryMetadata classes.
- Start EntryCache unit tests. The tests will be expanded in future CLs.
Change-Id: I726ec198c0c4188086357ac6df944a7670c30000
Diffstat (limited to 'pw_kvs/entry_cache.cc')
-rw-r--r-- | pw_kvs/entry_cache.cc | 202 |
1 files changed, 202 insertions, 0 deletions
diff --git a/pw_kvs/entry_cache.cc b/pw_kvs/entry_cache.cc new file mode 100644 index 000000000..eec4044be --- /dev/null +++ b/pw_kvs/entry_cache.cc @@ -0,0 +1,202 @@ +// Copyright 2020 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include "pw_kvs/internal/entry_cache.h" + +#include <cinttypes> + +#include "pw_kvs/flash_memory.h" +#include "pw_kvs/internal/entry.h" +#include "pw_kvs/internal/hash.h" +#include "pw_kvs_private/macros.h" +#include "pw_log/log.h" + +namespace pw::kvs::internal { +namespace { + +using std::string_view; + +constexpr FlashPartition::Address kNoAddress = FlashPartition::Address(-1); + +} // namespace + +void EntryMetadata::Reset(const KeyDescriptor& descriptor, Address address) { + *descriptor_ = descriptor; + + addresses_[0] = address; + for (size_t i = 1; i < addresses_.size(); ++i) { + addresses_[i] = kNoAddress; + } + addresses_ = addresses_.first(1); +} + +Status EntryCache::Find(FlashPartition& partition, + string_view key, + EntryMetadata* metadata) const { + const uint32_t hash = internal::Hash(key); + Entry::KeyBuffer key_buffer; + + for (size_t i = 0; i < descriptors_.size(); ++i) { + if (descriptors_[i].key_hash == hash) { + TRY(Entry::ReadKey( + partition, *first_address(i), key.size(), key_buffer.data())); + + if (key == string_view(key_buffer.data(), key.size())) { + PW_LOG_DEBUG("Found match for key hash 0x%08" PRIx32, hash); + *metadata = EntryMetadata(descriptors_[i], addresses(i)); + return Status::OK; + } else { + PW_LOG_WARN("Found key hash collision for 0x%08" PRIx32, hash); + return Status::ALREADY_EXISTS; + } + } + } + return Status::NOT_FOUND; +} + +Status EntryCache::FindExisting(FlashPartition& partition, + string_view key, + EntryMetadata* metadata) const { + Status status = Find(partition, key, metadata); + + // If the key's hash collides with an existing key or if the key is deleted, + // treat it as if it is not in the KVS. + if (status == Status::ALREADY_EXISTS || + (status.ok() && metadata->deleted())) { + return Status::NOT_FOUND; + } + return status; +} + +EntryMetadata EntryCache::AddNew(const KeyDescriptor& descriptor, + Address entry_address) { + // TODO(hepler): DCHECK(!full()); + Address* first_address = ResetAddresses(descriptors_.size(), entry_address); + descriptors_.push_back(descriptor); + return EntryMetadata(descriptors_.back(), span(first_address, 1)); +} + +// TODO: This method is the trigger of the O(valid_entries * all_entries) time +// complexity for reading. At some cost to memory, this could be optimized by +// using a hash table instead of scanning, but in practice this should be fine +// for a small number of keys +Status EntryCache::AddNewOrUpdateExisting(const KeyDescriptor& descriptor, + Address address, + size_t sector_size_bytes) { + // With the new key descriptor, either add it to the descriptor table or + // overwrite an existing entry with an older version of the key. + const int index = FindIndex(descriptor.key_hash); + + // Write a new entry if there is room. + if (index == -1) { + if (full()) { + return Status::RESOURCE_EXHAUSTED; + } + AddNew(descriptor, address); + return Status::OK; + } + + // Existing entry is old; replace the existing entry with the new one. + if (descriptor.transaction_id > descriptors_[index].transaction_id) { + descriptors_[index] = descriptor; + ResetAddresses(index, address); + return Status::OK; + } + + // If the entries have a duplicate transaction ID, add the new (redundant) + // entry to the existing descriptor. + if (descriptors_[index].transaction_id == descriptor.transaction_id) { + if (descriptors_[index].key_hash != descriptor.key_hash) { + PW_LOG_ERROR("Duplicate entry for key 0x%08" PRIx32 + " with transaction ID %" PRIu32 " has non-matching hash", + descriptor.key_hash, + descriptor.transaction_id); + return Status::DATA_LOSS; + } + + // Verify that this entry is not in the same sector as an existing copy of + // this same key. + for (Address existing_address : addresses(index)) { + if (existing_address / sector_size_bytes == address / sector_size_bytes) { + PW_LOG_DEBUG("Multiple Redundant entries in same sector %zu", + address / sector_size_bytes); + return Status::DATA_LOSS; + } + } + + AddAddressIfRoom(index, address); + } else { + PW_LOG_DEBUG("Found stale entry when appending; ignoring"); + } + return Status::OK; +} + +size_t EntryCache::present_entries() const { + size_t present_entries = 0; + + for (const KeyDescriptor& descriptor : descriptors_) { + if (descriptor.state != EntryState::kDeleted) { + present_entries += 1; + } + } + + return present_entries; +} + +int EntryCache::FindIndex(uint32_t key_hash) const { + for (size_t i = 0; i < descriptors_.size(); ++i) { + if (descriptors_[i].key_hash == key_hash) { + return i; + } + } + return -1; +} + +void EntryCache::AddAddressIfRoom(size_t descriptor_index, Address address) { + Address* const existing = first_address(descriptor_index); + + for (size_t i = 0; i < redundancy(); ++i) { + if (existing[i] == kNoAddress) { + existing[i] = address; + addresses(descriptor_index); + return; + } + } +} + +span<EntryCache::Address> EntryCache::addresses(size_t descriptor_index) const { + Address* const addresses = first_address(descriptor_index); + + size_t size = 0; + while (size < redundancy() && addresses[size] != kNoAddress) { + size += 1; + } + + return span(addresses, size); +} + +EntryCache::Address* EntryCache::ResetAddresses(size_t descriptor_index, + Address address) { + Address* first = first_address(descriptor_index); + *first = address; + + // Clear the additional addresses, if any. + for (size_t i = 1; i < redundancy_; ++i) { + first[i] = kNoAddress; + } + + return first; +} + +} // namespace pw::kvs::internal |