/* * SnapshotCache.h * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project 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 * * http://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. */ #ifndef FDBCLIENT_SNAPSHOTCACHE_H #define FDBCLIENT_SNAPSHOTCACHE_H #pragma once #include "fdbclient/FDBTypes.h" #include "fdbclient/NativeAPI.actor.h" #include "fdbclient/SystemData.h" #include "flow/IndexedSet.h" struct ExtStringRef { ExtStringRef() : extra_zero_bytes(0) {} ExtStringRef(StringRef const& s, int extra_zero_bytes = 0) : base(s), extra_zero_bytes(extra_zero_bytes) {} Standalone toStandaloneStringRef() const { auto s = makeString(size()); if (base.size() > 0) { memcpy(mutateString(s), base.begin(), base.size()); } memset(mutateString(s) + base.size(), 0, extra_zero_bytes); return s; }; StringRef toArenaOrRef(Arena& a) const { if (extra_zero_bytes) { StringRef dest = StringRef(new (a) uint8_t[size()], size()); if (base.size() > 0) { memcpy(mutateString(dest), base.begin(), base.size()); } memset(mutateString(dest) + base.size(), 0, extra_zero_bytes); return dest; } else return base; } StringRef assertRef() const { ASSERT(extra_zero_bytes == 0); return base; } StringRef toArena(Arena& a) const { if (extra_zero_bytes) { StringRef dest = StringRef(new (a) uint8_t[size()], size()); if (base.size() > 0) { memcpy(mutateString(dest), base.begin(), base.size()); } memset(mutateString(dest) + base.size(), 0, extra_zero_bytes); return dest; } else return StringRef(a, base); } int size() const { return base.size() + extra_zero_bytes; } int compare(ExtStringRef const& rhs) const { int cbl = std::min(base.size(), rhs.base.size()); if (cbl > 0) { int c = memcmp(base.begin(), rhs.base.begin(), cbl); if (c != 0) return c; } for (int i = cbl; i < base.size(); i++) if (base[i]) return 1; for (int i = cbl; i < rhs.base.size(); i++) if (rhs.base[i]) return -1; return ::compare(size(), rhs.size()); } bool startsWith(const ExtStringRef& s) const { if (size() < s.size()) return false; int cbl = std::min(base.size(), s.base.size()); for (int i = cbl; i < std::min(s.size(), base.size()); i++) if (base[i]) return false; for (int i = cbl; i < s.base.size(); i++) if (s.base[i]) return false; return !memcmp(base.begin(), s.base.begin(), cbl); } bool isKeyAfter(ExtStringRef const& s) const { if (size() != s.size() + 1) return false; if (extra_zero_bytes == 0 && base[base.size() - 1] != 0) return false; return startsWith(s); } ExtStringRef keyAfter() const { return ExtStringRef(base, extra_zero_bytes + 1); } private: friend struct Traceable; StringRef base; int extra_zero_bytes; }; inline bool operator==(const ExtStringRef& lhs, const ExtStringRef& rhs) { return lhs.size() == rhs.size() && !lhs.compare(rhs); } inline bool operator!=(const ExtStringRef& lhs, const ExtStringRef& rhs) { return !(lhs == rhs); } inline bool operator<(const ExtStringRef& lhs, const ExtStringRef& rhs) { return lhs.compare(rhs) < 0; } inline bool operator>(const ExtStringRef& lhs, const ExtStringRef& rhs) { return lhs.compare(rhs) > 0; } inline bool operator<=(const ExtStringRef& lhs, const ExtStringRef& rhs) { return lhs.compare(rhs) <= 0; } inline bool operator>=(const ExtStringRef& lhs, const ExtStringRef& rhs) { return lhs.compare(rhs) >= 0; } template <> struct Traceable : std::true_type { static std::string toString(const ExtStringRef str) { std::string result; result.reserve(str.size()); std::copy(str.base.begin(), str.base.end(), std::back_inserter(result)); for (int i = 0; i < str.extra_zero_bytes; ++i) { result.push_back('\0'); } return Traceable::toString(result); } }; class SnapshotCache { private: struct Entry { // An entry represents a range of keys which are known. The keys which are not present in `values` are // implicitly empty. KeyRef beginKey; ExtStringRef endKey; VectorRef values; Entry(KeyRef const& beginKey, ExtStringRef const& endKey, VectorRef const& values) : beginKey(beginKey), endKey(endKey), values(values) {} Entry(KeyValueRef const& kv, Arena& arena) : beginKey(kv.key), endKey(kv.key, 1) { values.push_back(arena, kv); } int compare(Entry const& r) const { return ::compare(beginKey, r.beginKey); } bool operator<(Entry const& r) const { return beginKey < r.beginKey; } int segments() const { return 2 * (values.size() + 1); } }; friend class ReadYourWritesTransaction; Arena* arena; IndexedSet entries; public: struct iterator { // Iterates over three types of segments: individual key/value pairs, empty ranges, and ranges which are not // known. Every key will belong to exactly one segment. The first segment begins at "" and the last segment // ends at \xff\xff. // Note that an uncached range stops at the next individual key that is known, even though we might want to read // through that key. In RYWIterator, it might also stop at a dependent write which is not known at all! iterator(SnapshotCache* cache) : parent(cache), it(cache->entries.begin()), offset(0) { //++*this; // gives begin } iterator(SnapshotCache* cache, class WriteMap* writes) : parent(cache), it(cache->entries.begin()), offset(0) { } // for RYW to use the same constructor for snapshot cache and RYWIterator enum SEGMENT_TYPE { UNKNOWN_RANGE, EMPTY_RANGE, KV }; SEGMENT_TYPE type() const { if (!offset) return UNKNOWN_RANGE; if (offset & 1) return EMPTY_RANGE; return KV; } bool is_kv() const { return type() == KV; } bool is_unknown_range() const { return type() == UNKNOWN_RANGE; } bool is_empty_range() const { return type() == EMPTY_RANGE; } bool is_dependent() const { return false; } bool is_unreadable() const { return false; } void bypassUnreadableProtection() {} ExtStringRef beginKey() const { if (offset == 0) { auto prev = it; prev.decrementNonEnd(); return prev->endKey; } else if (offset == 1) return it->beginKey; else return ExtStringRef(it->values[(offset - 2) >> 1].key, offset & 1); } ExtStringRef endKey() const { if (offset == 0) return it->beginKey; else if (offset == it->segments() - 1) return it->endKey; else return ExtStringRef(it->values[(offset - 1) >> 1].key, 1 - (offset & 1)); } const KeyValueRef* kv(Arena& arena) const { // only if is_kv() return &it->values[(offset - 2) >> 1]; } iterator& operator++() { ExtStringRef originalEnd = endKey(); do { offset++; if (offset == it->segments()) { offset = 0; ++it; } } while (endKey() == originalEnd); // TODO: pointer only comparison; maintain equality of pointers to keys // around degenerate segments return *this; } iterator& operator--() { ExtStringRef originalBegin = beginKey(); do { offset--; if (offset < 0) { it.decrementNonEnd(); offset = it->segments() - 1; } } while (beginKey() == originalBegin); return *this; } bool operator==(const iterator& r) const { return it == r.it && offset == r.offset; } bool operator!=(const iterator& r) const { return !(*this == r); } void skip( KeyRef key) { // Changes *this to the segment containing key (so that beginKey()<=key && key < endKey()) if (key == allKeys.end) { it = parent->entries.lastItem(); offset = 1; return; } it = parent->entries.lastLessOrEqual(Entry(key, key, VectorRef())); // TODO: finger query? if (key >= it->endKey) { offset = 0; ++it; } else { int idx = std::lower_bound(it->values.begin(), it->values.end(), key, KeyValueRef::OrderByKey()) - it->values.begin(); offset = idx * 2 + 1 + (idx < it->values.size() && it->values[idx].key == key); } } void skipContiguous(ExtStringRef key) { // Changes *this to be the last iterator i | the elements e of array // [&*this, &*i] all have e->key < key offset = 2 * (std::lower_bound(it->values.begin() + offset / 2, it->values.end(), key, KeyValueRef::OrderByKey()) - it->values.begin()); } void skipContiguousBack(ExtStringRef key) { // Changes *this to be the first iterator i | the elements e of // array [&*i, &*this] all have e->key >= key offset = 2 * (std::lower_bound( it->values.begin(), it->values.begin() + offset / 2 - 1, key, KeyValueRef::OrderByKey()) - it->values.begin()) + 2; } void _nextUnknown() { // For internal use only - can return a degenerate segment ++it; offset = 0; } void _prevUnknown() { // For internal use only - can return a degenerate segment offset = 0; } void dbg() {} private: friend class SnapshotCache; SnapshotCache* parent; IndexedSet::iterator it; int offset; // 0 <= offset < it->segments() }; explicit SnapshotCache(Arena* arena) : arena(arena) { // Degenerate entries at the beginning and end reduce edge cases entries.insert(Entry(allKeys.begin, allKeys.begin, VectorRef()), NoMetric(), true); entries.insert(Entry(allKeys.end, afterAllKeys, VectorRef()), NoMetric(), true); } // Visual Studio refuses to generate these, apparently despite the standard SnapshotCache(SnapshotCache&& r) noexcept : entries(std::move(r.entries)), arena(r.arena) {} SnapshotCache& operator=(SnapshotCache&& r) noexcept { entries = std::move(r.entries); arena = r.arena; return *this; } bool empty() const { // Returns true iff anything is known about the contents of the snapshot for (auto i = entries.begin(); i != entries.end(); ++i) if (i->beginKey != i->endKey && i->beginKey < allKeys.end) return false; return true; } bool insert(KeyRef key, Optional value) { // Asserts that, in the snapshot, the given key has the given value (or is not present, if !value.present()) iterator it(this); it.skip(key); if (it.is_unknown_range()) { if (value.present()) entries.insert(Entry(KeyValueRef(key, value.get()), *arena), NoMetric(), true); else entries.insert(Entry(key, ExtStringRef(key, 1), VectorRef()), NoMetric(), true); return true; } return false; } bool insert(KeyRangeRef keys, VectorRef values) { // Asserts that, in the snapshot, the given ranges of keys contains (only) the given key/value pairs // The returned iterator points to the first key in the range, or after the range if !values.size() if (keys.empty()) return false; iterator itb(this); itb.skip(keys.begin); iterator ite = itb; ite.skip(keys.end); StringRef begin = keys.begin; if (!itb.is_unknown_range() && itb.it->beginKey != keys.begin) { begin = itb.it->endKey.toArenaOrRef(*arena); auto i = std::lower_bound(values.begin(), values.end(), begin, KeyValueRef::OrderByKey()); values = VectorRef(i, values.end() - i); itb._nextUnknown(); } ExtStringRef end = keys.end; if (!ite.is_unknown_range()) { ite._prevUnknown(); end = ite.endKey(); values.resize(*arena, std::lower_bound(values.begin(), values.end(), end, KeyValueRef::OrderByKey()) - values.begin()); } if (begin < end) { bool addBegin = begin != allKeys.begin && itb.it->beginKey == allKeys.begin; entries.erase(itb.it, ite.it); entries.insert(Entry(begin, end, values), NoMetric(), true); if (addBegin) entries.insert(Entry(allKeys.begin, allKeys.begin, VectorRef()), NoMetric(), true); return true; } return false; } void dump() { for (auto it = entries.begin(); it != entries.end(); ++it) { TraceEvent("CacheDump") .detail("Begin", it->beginKey) .detail("End", it->endKey.toStandaloneStringRef()) .detail("Values", it->values); } } void promise(iterator const& segment, KeyRangeRef keys, Future onReady); // Asserts that the caller is reading the contents of the given range of keys and will later call insert(keys, ?) // and then set the given onReady future. // ? Also asserts that segment.uncached_range().begin() <= keys.begin && segment.uncached_range() >= keys.end // segment is not invalidated }; #endif