add leveldb
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/arena.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const int kBlockSize = 4096;
|
||||
|
||||
Arena::Arena()
|
||||
: alloc_ptr_(nullptr), alloc_bytes_remaining_(0), memory_usage_(0) {}
|
||||
|
||||
Arena::~Arena() {
|
||||
for (size_t i = 0; i < blocks_.size(); i++) {
|
||||
delete[] blocks_[i];
|
||||
}
|
||||
}
|
||||
|
||||
char* Arena::AllocateFallback(size_t bytes) {
|
||||
if (bytes > kBlockSize / 4) {
|
||||
// Object is more than a quarter of our block size. Allocate it separately
|
||||
// to avoid wasting too much space in leftover bytes.
|
||||
char* result = AllocateNewBlock(bytes);
|
||||
return result;
|
||||
}
|
||||
|
||||
// We waste the remaining space in the current block.
|
||||
alloc_ptr_ = AllocateNewBlock(kBlockSize);
|
||||
alloc_bytes_remaining_ = kBlockSize;
|
||||
|
||||
char* result = alloc_ptr_;
|
||||
alloc_ptr_ += bytes;
|
||||
alloc_bytes_remaining_ -= bytes;
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Arena::AllocateAligned(size_t bytes) {
|
||||
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
|
||||
static_assert((align & (align - 1)) == 0,
|
||||
"Pointer size should be a power of 2");
|
||||
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
|
||||
size_t slop = (current_mod == 0 ? 0 : align - current_mod);
|
||||
size_t needed = bytes + slop;
|
||||
char* result;
|
||||
if (needed <= alloc_bytes_remaining_) {
|
||||
result = alloc_ptr_ + slop;
|
||||
alloc_ptr_ += needed;
|
||||
alloc_bytes_remaining_ -= needed;
|
||||
} else {
|
||||
// AllocateFallback always returned aligned memory
|
||||
result = AllocateFallback(bytes);
|
||||
}
|
||||
assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
char* Arena::AllocateNewBlock(size_t block_bytes) {
|
||||
char* result = new char[block_bytes];
|
||||
blocks_.push_back(result);
|
||||
memory_usage_.fetch_add(block_bytes + sizeof(char*),
|
||||
std::memory_order_relaxed);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
#define STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Arena {
|
||||
public:
|
||||
Arena();
|
||||
|
||||
Arena(const Arena&) = delete;
|
||||
Arena& operator=(const Arena&) = delete;
|
||||
|
||||
~Arena();
|
||||
|
||||
// Return a pointer to a newly allocated memory block of "bytes" bytes.
|
||||
char* Allocate(size_t bytes);
|
||||
|
||||
// Allocate memory with the normal alignment guarantees provided by malloc.
|
||||
char* AllocateAligned(size_t bytes);
|
||||
|
||||
// Returns an estimate of the total memory usage of data allocated
|
||||
// by the arena.
|
||||
size_t MemoryUsage() const {
|
||||
return memory_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
char* AllocateFallback(size_t bytes);
|
||||
char* AllocateNewBlock(size_t block_bytes);
|
||||
|
||||
// Allocation state
|
||||
char* alloc_ptr_;
|
||||
size_t alloc_bytes_remaining_;
|
||||
|
||||
// Array of new[] allocated memory blocks
|
||||
std::vector<char*> blocks_;
|
||||
|
||||
// Total memory usage of the arena.
|
||||
//
|
||||
// TODO(costan): This member is accessed via atomics, but the others are
|
||||
// accessed without any locking. Is this OK?
|
||||
std::atomic<size_t> memory_usage_;
|
||||
};
|
||||
|
||||
inline char* Arena::Allocate(size_t bytes) {
|
||||
// The semantics of what to return are a bit messy if we allow
|
||||
// 0-byte allocations, so we disallow them here (we don't need
|
||||
// them for our internal use).
|
||||
assert(bytes > 0);
|
||||
if (bytes <= alloc_bytes_remaining_) {
|
||||
char* result = alloc_ptr_;
|
||||
alloc_ptr_ += bytes;
|
||||
alloc_bytes_remaining_ -= bytes;
|
||||
return result;
|
||||
}
|
||||
return AllocateFallback(bytes);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/arena.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
TEST(ArenaTest, Empty) { Arena arena; }
|
||||
|
||||
TEST(ArenaTest, Simple) {
|
||||
std::vector<std::pair<size_t, char*>> allocated;
|
||||
Arena arena;
|
||||
const int N = 100000;
|
||||
size_t bytes = 0;
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < N; i++) {
|
||||
size_t s;
|
||||
if (i % (N / 10) == 0) {
|
||||
s = i;
|
||||
} else {
|
||||
s = rnd.OneIn(4000)
|
||||
? rnd.Uniform(6000)
|
||||
: (rnd.OneIn(10) ? rnd.Uniform(100) : rnd.Uniform(20));
|
||||
}
|
||||
if (s == 0) {
|
||||
// Our arena disallows size 0 allocations.
|
||||
s = 1;
|
||||
}
|
||||
char* r;
|
||||
if (rnd.OneIn(10)) {
|
||||
r = arena.AllocateAligned(s);
|
||||
} else {
|
||||
r = arena.Allocate(s);
|
||||
}
|
||||
|
||||
for (size_t b = 0; b < s; b++) {
|
||||
// Fill the "i"th allocation with a known bit pattern
|
||||
r[b] = i % 256;
|
||||
}
|
||||
bytes += s;
|
||||
allocated.push_back(std::make_pair(s, r));
|
||||
ASSERT_GE(arena.MemoryUsage(), bytes);
|
||||
if (i > N / 10) {
|
||||
ASSERT_LE(arena.MemoryUsage(), bytes * 1.10);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < allocated.size(); i++) {
|
||||
size_t num_bytes = allocated[i].first;
|
||||
const char* p = allocated[i].second;
|
||||
for (size_t b = 0; b < num_bytes; b++) {
|
||||
// Check the "i"th allocation for the known bit pattern
|
||||
ASSERT_EQ(int(p[b]) & 0xff, i % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2012 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/filter_policy.h"
|
||||
|
||||
#include "leveldb/slice.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
static uint32_t BloomHash(const Slice& key) {
|
||||
return Hash(key.data(), key.size(), 0xbc9f1d34);
|
||||
}
|
||||
|
||||
class BloomFilterPolicy : public FilterPolicy {
|
||||
public:
|
||||
explicit BloomFilterPolicy(int bits_per_key) : bits_per_key_(bits_per_key) {
|
||||
// We intentionally round down to reduce probing cost a little bit
|
||||
k_ = static_cast<size_t>(bits_per_key * 0.69); // 0.69 =~ ln(2)
|
||||
if (k_ < 1) k_ = 1;
|
||||
if (k_ > 30) k_ = 30;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "leveldb.BuiltinBloomFilter2"; }
|
||||
|
||||
void CreateFilter(const Slice* keys, int n, std::string* dst) const override {
|
||||
// Compute bloom filter size (in both bits and bytes)
|
||||
size_t bits = n * bits_per_key_;
|
||||
|
||||
// For small n, we can see a very high false positive rate. Fix it
|
||||
// by enforcing a minimum bloom filter length.
|
||||
if (bits < 64) bits = 64;
|
||||
|
||||
size_t bytes = (bits + 7) / 8;
|
||||
bits = bytes * 8;
|
||||
|
||||
const size_t init_size = dst->size();
|
||||
dst->resize(init_size + bytes, 0);
|
||||
dst->push_back(static_cast<char>(k_)); // Remember # of probes in filter
|
||||
char* array = &(*dst)[init_size];
|
||||
for (int i = 0; i < n; i++) {
|
||||
// Use double-hashing to generate a sequence of hash values.
|
||||
// See analysis in [Kirsch,Mitzenmacher 2006].
|
||||
uint32_t h = BloomHash(keys[i]);
|
||||
const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
|
||||
for (size_t j = 0; j < k_; j++) {
|
||||
const uint32_t bitpos = h % bits;
|
||||
array[bitpos / 8] |= (1 << (bitpos % 8));
|
||||
h += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyMayMatch(const Slice& key, const Slice& bloom_filter) const override {
|
||||
const size_t len = bloom_filter.size();
|
||||
if (len < 2) return false;
|
||||
|
||||
const char* array = bloom_filter.data();
|
||||
const size_t bits = (len - 1) * 8;
|
||||
|
||||
// Use the encoded k so that we can read filters generated by
|
||||
// bloom filters created using different parameters.
|
||||
const size_t k = array[len - 1];
|
||||
if (k > 30) {
|
||||
// Reserved for potentially new encodings for short bloom filters.
|
||||
// Consider it a match.
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t h = BloomHash(key);
|
||||
const uint32_t delta = (h >> 17) | (h << 15); // Rotate right 17 bits
|
||||
for (size_t j = 0; j < k; j++) {
|
||||
const uint32_t bitpos = h % bits;
|
||||
if ((array[bitpos / 8] & (1 << (bitpos % 8))) == 0) return false;
|
||||
h += delta;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t bits_per_key_;
|
||||
size_t k_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
const FilterPolicy* NewBloomFilterPolicy(int bits_per_key) {
|
||||
return new BloomFilterPolicy(bits_per_key);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2012 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "leveldb/filter_policy.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const int kVerbose = 1;
|
||||
|
||||
static Slice Key(int i, char* buffer) {
|
||||
EncodeFixed32(buffer, i);
|
||||
return Slice(buffer, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
class BloomTest : public testing::Test {
|
||||
public:
|
||||
BloomTest() : policy_(NewBloomFilterPolicy(10)) {}
|
||||
|
||||
~BloomTest() { delete policy_; }
|
||||
|
||||
void Reset() {
|
||||
keys_.clear();
|
||||
filter_.clear();
|
||||
}
|
||||
|
||||
void Add(const Slice& s) { keys_.push_back(s.ToString()); }
|
||||
|
||||
void Build() {
|
||||
std::vector<Slice> key_slices;
|
||||
for (size_t i = 0; i < keys_.size(); i++) {
|
||||
key_slices.push_back(Slice(keys_[i]));
|
||||
}
|
||||
filter_.clear();
|
||||
policy_->CreateFilter(&key_slices[0], static_cast<int>(key_slices.size()),
|
||||
&filter_);
|
||||
keys_.clear();
|
||||
if (kVerbose >= 2) DumpFilter();
|
||||
}
|
||||
|
||||
size_t FilterSize() const { return filter_.size(); }
|
||||
|
||||
void DumpFilter() {
|
||||
std::fprintf(stderr, "F(");
|
||||
for (size_t i = 0; i + 1 < filter_.size(); i++) {
|
||||
const unsigned int c = static_cast<unsigned int>(filter_[i]);
|
||||
for (int j = 0; j < 8; j++) {
|
||||
std::fprintf(stderr, "%c", (c & (1 << j)) ? '1' : '.');
|
||||
}
|
||||
}
|
||||
std::fprintf(stderr, ")\n");
|
||||
}
|
||||
|
||||
bool Matches(const Slice& s) {
|
||||
if (!keys_.empty()) {
|
||||
Build();
|
||||
}
|
||||
return policy_->KeyMayMatch(s, filter_);
|
||||
}
|
||||
|
||||
double FalsePositiveRate() {
|
||||
char buffer[sizeof(int)];
|
||||
int result = 0;
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
if (Matches(Key(i + 1000000000, buffer))) {
|
||||
result++;
|
||||
}
|
||||
}
|
||||
return result / 10000.0;
|
||||
}
|
||||
|
||||
private:
|
||||
const FilterPolicy* policy_;
|
||||
std::string filter_;
|
||||
std::vector<std::string> keys_;
|
||||
};
|
||||
|
||||
TEST_F(BloomTest, EmptyFilter) {
|
||||
ASSERT_TRUE(!Matches("hello"));
|
||||
ASSERT_TRUE(!Matches("world"));
|
||||
}
|
||||
|
||||
TEST_F(BloomTest, Small) {
|
||||
Add("hello");
|
||||
Add("world");
|
||||
ASSERT_TRUE(Matches("hello"));
|
||||
ASSERT_TRUE(Matches("world"));
|
||||
ASSERT_TRUE(!Matches("x"));
|
||||
ASSERT_TRUE(!Matches("foo"));
|
||||
}
|
||||
|
||||
static int NextLength(int length) {
|
||||
if (length < 10) {
|
||||
length += 1;
|
||||
} else if (length < 100) {
|
||||
length += 10;
|
||||
} else if (length < 1000) {
|
||||
length += 100;
|
||||
} else {
|
||||
length += 1000;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
TEST_F(BloomTest, VaryingLengths) {
|
||||
char buffer[sizeof(int)];
|
||||
|
||||
// Count number of filters that significantly exceed the false positive rate
|
||||
int mediocre_filters = 0;
|
||||
int good_filters = 0;
|
||||
|
||||
for (int length = 1; length <= 10000; length = NextLength(length)) {
|
||||
Reset();
|
||||
for (int i = 0; i < length; i++) {
|
||||
Add(Key(i, buffer));
|
||||
}
|
||||
Build();
|
||||
|
||||
ASSERT_LE(FilterSize(), static_cast<size_t>((length * 10 / 8) + 40))
|
||||
<< length;
|
||||
|
||||
// All added keys must match
|
||||
for (int i = 0; i < length; i++) {
|
||||
ASSERT_TRUE(Matches(Key(i, buffer)))
|
||||
<< "Length " << length << "; key " << i;
|
||||
}
|
||||
|
||||
// Check false positive rate
|
||||
double rate = FalsePositiveRate();
|
||||
if (kVerbose >= 1) {
|
||||
std::fprintf(stderr,
|
||||
"False positives: %5.2f%% @ length = %6d ; bytes = %6d\n",
|
||||
rate * 100.0, length, static_cast<int>(FilterSize()));
|
||||
}
|
||||
ASSERT_LE(rate, 0.02); // Must not be over 2%
|
||||
if (rate > 0.0125)
|
||||
mediocre_filters++; // Allowed, but not too often
|
||||
else
|
||||
good_filters++;
|
||||
}
|
||||
if (kVerbose >= 1) {
|
||||
std::fprintf(stderr, "Filters: %d good, %d mediocre\n", good_filters,
|
||||
mediocre_filters);
|
||||
}
|
||||
ASSERT_LE(mediocre_filters, good_filters / 5);
|
||||
}
|
||||
|
||||
// Different bits-per-byte
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
Cache::~Cache() {}
|
||||
|
||||
namespace {
|
||||
|
||||
// LRU cache implementation
|
||||
//
|
||||
// Cache entries have an "in_cache" boolean indicating whether the cache has a
|
||||
// reference on the entry. The only ways that this can become false without the
|
||||
// entry being passed to its "deleter" are via Erase(), via Insert() when
|
||||
// an element with a duplicate key is inserted, or on destruction of the cache.
|
||||
//
|
||||
// The cache keeps two linked lists of items in the cache. All items in the
|
||||
// cache are in one list or the other, and never both. Items still referenced
|
||||
// by clients but erased from the cache are in neither list. The lists are:
|
||||
// - in-use: contains the items currently referenced by clients, in no
|
||||
// particular order. (This list is used for invariant checking. If we
|
||||
// removed the check, elements that would otherwise be on this list could be
|
||||
// left as disconnected singleton lists.)
|
||||
// - LRU: contains the items not currently referenced by clients, in LRU order
|
||||
// Elements are moved between these lists by the Ref() and Unref() methods,
|
||||
// when they detect an element in the cache acquiring or losing its only
|
||||
// external reference.
|
||||
|
||||
// An entry is a variable length heap-allocated structure. Entries
|
||||
// are kept in a circular doubly linked list ordered by access time.
|
||||
struct LRUHandle {
|
||||
void* value;
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
LRUHandle* next_hash;
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t key_length;
|
||||
bool in_cache; // Whether entry is in the cache.
|
||||
uint32_t refs; // References, including cache reference, if present.
|
||||
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
|
||||
char key_data[1]; // Beginning of key
|
||||
|
||||
Slice key() const {
|
||||
// next_ is only equal to this if the LRU handle is the list head of an
|
||||
// empty list. List heads never have meaningful keys.
|
||||
assert(next != this);
|
||||
|
||||
return Slice(key_data, key_length);
|
||||
}
|
||||
};
|
||||
|
||||
// We provide our own simple hash table since it removes a whole bunch
|
||||
// of porting hacks and is also faster than some of the built-in hash
|
||||
// table implementations in some of the compiler/runtime combinations
|
||||
// we have tested. E.g., readrandom speeds up by ~5% over the g++
|
||||
// 4.4.3's builtin hashtable.
|
||||
class HandleTable {
|
||||
public:
|
||||
HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); }
|
||||
~HandleTable() { delete[] list_; }
|
||||
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash) {
|
||||
return *FindPointer(key, hash);
|
||||
}
|
||||
|
||||
LRUHandle* Insert(LRUHandle* h) {
|
||||
LRUHandle** ptr = FindPointer(h->key(), h->hash);
|
||||
LRUHandle* old = *ptr;
|
||||
h->next_hash = (old == nullptr ? nullptr : old->next_hash);
|
||||
*ptr = h;
|
||||
if (old == nullptr) {
|
||||
++elems_;
|
||||
if (elems_ > length_) {
|
||||
// Since each cache entry is fairly large, we aim for a small
|
||||
// average linked list length (<= 1).
|
||||
Resize();
|
||||
}
|
||||
}
|
||||
return old;
|
||||
}
|
||||
|
||||
LRUHandle* Remove(const Slice& key, uint32_t hash) {
|
||||
LRUHandle** ptr = FindPointer(key, hash);
|
||||
LRUHandle* result = *ptr;
|
||||
if (result != nullptr) {
|
||||
*ptr = result->next_hash;
|
||||
--elems_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
// The table consists of an array of buckets where each bucket is
|
||||
// a linked list of cache entries that hash into the bucket.
|
||||
uint32_t length_;
|
||||
uint32_t elems_;
|
||||
LRUHandle** list_;
|
||||
|
||||
// Return a pointer to slot that points to a cache entry that
|
||||
// matches key/hash. If there is no such cache entry, return a
|
||||
// pointer to the trailing slot in the corresponding linked list.
|
||||
LRUHandle** FindPointer(const Slice& key, uint32_t hash) {
|
||||
LRUHandle** ptr = &list_[hash & (length_ - 1)];
|
||||
while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
|
||||
ptr = &(*ptr)->next_hash;
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Resize() {
|
||||
uint32_t new_length = 4;
|
||||
while (new_length < elems_) {
|
||||
new_length *= 2;
|
||||
}
|
||||
LRUHandle** new_list = new LRUHandle*[new_length];
|
||||
memset(new_list, 0, sizeof(new_list[0]) * new_length);
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < length_; i++) {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != nullptr) {
|
||||
LRUHandle* next = h->next_hash;
|
||||
uint32_t hash = h->hash;
|
||||
LRUHandle** ptr = &new_list[hash & (new_length - 1)];
|
||||
h->next_hash = *ptr;
|
||||
*ptr = h;
|
||||
h = next;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
assert(elems_ == count);
|
||||
delete[] list_;
|
||||
list_ = new_list;
|
||||
length_ = new_length;
|
||||
}
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
class LRUCache {
|
||||
public:
|
||||
LRUCache();
|
||||
~LRUCache();
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
void SetCapacity(size_t capacity) { capacity_ = capacity; }
|
||||
|
||||
// Like Cache methods, but with an extra "hash" parameter.
|
||||
Cache::Handle* Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value));
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash);
|
||||
void Release(Cache::Handle* handle);
|
||||
void Erase(const Slice& key, uint32_t hash);
|
||||
void Prune();
|
||||
size_t TotalCharge() const {
|
||||
MutexLock l(&mutex_);
|
||||
return usage_;
|
||||
}
|
||||
|
||||
private:
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Append(LRUHandle* list, LRUHandle* e);
|
||||
void Ref(LRUHandle* e);
|
||||
void Unref(LRUHandle* e);
|
||||
bool FinishErase(LRUHandle* e) EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Initialized before use.
|
||||
size_t capacity_;
|
||||
|
||||
// mutex_ protects the following state.
|
||||
mutable port::Mutex mutex_;
|
||||
size_t usage_ GUARDED_BY(mutex_);
|
||||
|
||||
// Dummy head of LRU list.
|
||||
// lru.prev is newest entry, lru.next is oldest entry.
|
||||
// Entries have refs==1 and in_cache==true.
|
||||
LRUHandle lru_ GUARDED_BY(mutex_);
|
||||
|
||||
// Dummy head of in-use list.
|
||||
// Entries are in use by clients, and have refs >= 2 and in_cache==true.
|
||||
LRUHandle in_use_ GUARDED_BY(mutex_);
|
||||
|
||||
HandleTable table_ GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
LRUCache::LRUCache() : capacity_(0), usage_(0) {
|
||||
// Make empty circular linked lists.
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
in_use_.next = &in_use_;
|
||||
in_use_.prev = &in_use_;
|
||||
}
|
||||
|
||||
LRUCache::~LRUCache() {
|
||||
assert(in_use_.next == &in_use_); // Error if caller has an unreleased handle
|
||||
for (LRUHandle* e = lru_.next; e != &lru_;) {
|
||||
LRUHandle* next = e->next;
|
||||
assert(e->in_cache);
|
||||
e->in_cache = false;
|
||||
assert(e->refs == 1); // Invariant of lru_ list.
|
||||
Unref(e);
|
||||
e = next;
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCache::Ref(LRUHandle* e) {
|
||||
if (e->refs == 1 && e->in_cache) { // If on lru_ list, move to in_use_ list.
|
||||
LRU_Remove(e);
|
||||
LRU_Append(&in_use_, e);
|
||||
}
|
||||
e->refs++;
|
||||
}
|
||||
|
||||
void LRUCache::Unref(LRUHandle* e) {
|
||||
assert(e->refs > 0);
|
||||
e->refs--;
|
||||
if (e->refs == 0) { // Deallocate.
|
||||
assert(!e->in_cache);
|
||||
(*e->deleter)(e->key(), e->value);
|
||||
free(e);
|
||||
} else if (e->in_cache && e->refs == 1) {
|
||||
// No longer in use; move to lru_ list.
|
||||
LRU_Remove(e);
|
||||
LRU_Append(&lru_, e);
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCache::LRU_Remove(LRUHandle* e) {
|
||||
e->next->prev = e->prev;
|
||||
e->prev->next = e->next;
|
||||
}
|
||||
|
||||
void LRUCache::LRU_Append(LRUHandle* list, LRUHandle* e) {
|
||||
// Make "e" newest entry by inserting just before *list
|
||||
e->next = list;
|
||||
e->prev = list->prev;
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) {
|
||||
MutexLock l(&mutex_);
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
Ref(e);
|
||||
}
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
|
||||
void LRUCache::Release(Cache::Handle* handle) {
|
||||
MutexLock l(&mutex_);
|
||||
Unref(reinterpret_cast<LRUHandle*>(handle));
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCache::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key,
|
||||
void* value)) {
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
LRUHandle* e =
|
||||
reinterpret_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
|
||||
e->value = value;
|
||||
e->deleter = deleter;
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->in_cache = false;
|
||||
e->refs = 1; // for the returned handle.
|
||||
std::memcpy(e->key_data, key.data(), key.size());
|
||||
|
||||
if (capacity_ > 0) {
|
||||
e->refs++; // for the cache's reference.
|
||||
e->in_cache = true;
|
||||
LRU_Append(&in_use_, e);
|
||||
usage_ += charge;
|
||||
FinishErase(table_.Insert(e));
|
||||
} else { // don't cache. (capacity_==0 is supported and turns off caching.)
|
||||
// next is read by key() in an assert, so it must be initialized
|
||||
e->next = nullptr;
|
||||
}
|
||||
while (usage_ > capacity_ && lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
assert(old->refs == 1);
|
||||
bool erased = FinishErase(table_.Remove(old->key(), old->hash));
|
||||
if (!erased) { // to avoid unused variable when compiled NDEBUG
|
||||
assert(erased);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
|
||||
// If e != nullptr, finish removing *e from the cache; it has already been
|
||||
// removed from the hash table. Return whether e != nullptr.
|
||||
bool LRUCache::FinishErase(LRUHandle* e) {
|
||||
if (e != nullptr) {
|
||||
assert(e->in_cache);
|
||||
LRU_Remove(e);
|
||||
e->in_cache = false;
|
||||
usage_ -= e->charge;
|
||||
Unref(e);
|
||||
}
|
||||
return e != nullptr;
|
||||
}
|
||||
|
||||
void LRUCache::Erase(const Slice& key, uint32_t hash) {
|
||||
MutexLock l(&mutex_);
|
||||
FinishErase(table_.Remove(key, hash));
|
||||
}
|
||||
|
||||
void LRUCache::Prune() {
|
||||
MutexLock l(&mutex_);
|
||||
while (lru_.next != &lru_) {
|
||||
LRUHandle* e = lru_.next;
|
||||
assert(e->refs == 1);
|
||||
bool erased = FinishErase(table_.Remove(e->key(), e->hash));
|
||||
if (!erased) { // to avoid unused variable when compiled NDEBUG
|
||||
assert(erased);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const int kNumShardBits = 4;
|
||||
static const int kNumShards = 1 << kNumShardBits;
|
||||
|
||||
class ShardedLRUCache : public Cache {
|
||||
private:
|
||||
LRUCache shard_[kNumShards];
|
||||
port::Mutex id_mutex_;
|
||||
uint64_t last_id_;
|
||||
|
||||
static inline uint32_t HashSlice(const Slice& s) {
|
||||
return Hash(s.data(), s.size(), 0);
|
||||
}
|
||||
|
||||
static uint32_t Shard(uint32_t hash) { return hash >> (32 - kNumShardBits); }
|
||||
|
||||
public:
|
||||
explicit ShardedLRUCache(size_t capacity) : last_id_(0) {
|
||||
const size_t per_shard = (capacity + (kNumShards - 1)) / kNumShards;
|
||||
for (int s = 0; s < kNumShards; s++) {
|
||||
shard_[s].SetCapacity(per_shard);
|
||||
}
|
||||
}
|
||||
~ShardedLRUCache() override {}
|
||||
Handle* Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value)) override {
|
||||
const uint32_t hash = HashSlice(key);
|
||||
return shard_[Shard(hash)].Insert(key, hash, value, charge, deleter);
|
||||
}
|
||||
Handle* Lookup(const Slice& key) override {
|
||||
const uint32_t hash = HashSlice(key);
|
||||
return shard_[Shard(hash)].Lookup(key, hash);
|
||||
}
|
||||
void Release(Handle* handle) override {
|
||||
LRUHandle* h = reinterpret_cast<LRUHandle*>(handle);
|
||||
shard_[Shard(h->hash)].Release(handle);
|
||||
}
|
||||
void Erase(const Slice& key) override {
|
||||
const uint32_t hash = HashSlice(key);
|
||||
shard_[Shard(hash)].Erase(key, hash);
|
||||
}
|
||||
void* Value(Handle* handle) override {
|
||||
return reinterpret_cast<LRUHandle*>(handle)->value;
|
||||
}
|
||||
uint64_t NewId() override {
|
||||
MutexLock l(&id_mutex_);
|
||||
return ++(last_id_);
|
||||
}
|
||||
void Prune() override {
|
||||
for (int s = 0; s < kNumShards; s++) {
|
||||
shard_[s].Prune();
|
||||
}
|
||||
}
|
||||
size_t TotalCharge() const override {
|
||||
size_t total = 0;
|
||||
for (int s = 0; s < kNumShards; s++) {
|
||||
total += shard_[s].TotalCharge();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
};
|
||||
|
||||
} // end anonymous namespace
|
||||
|
||||
Cache* NewLRUCache(size_t capacity) { return new ShardedLRUCache(capacity); }
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/cache.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Conversions between numeric keys/values and the types expected by Cache.
|
||||
static std::string EncodeKey(int k) {
|
||||
std::string result;
|
||||
PutFixed32(&result, k);
|
||||
return result;
|
||||
}
|
||||
static int DecodeKey(const Slice& k) {
|
||||
assert(k.size() == 4);
|
||||
return DecodeFixed32(k.data());
|
||||
}
|
||||
static void* EncodeValue(uintptr_t v) { return reinterpret_cast<void*>(v); }
|
||||
static int DecodeValue(void* v) { return reinterpret_cast<uintptr_t>(v); }
|
||||
|
||||
class CacheTest : public testing::Test {
|
||||
public:
|
||||
static void Deleter(const Slice& key, void* v) {
|
||||
current_->deleted_keys_.push_back(DecodeKey(key));
|
||||
current_->deleted_values_.push_back(DecodeValue(v));
|
||||
}
|
||||
|
||||
static constexpr int kCacheSize = 1000;
|
||||
std::vector<int> deleted_keys_;
|
||||
std::vector<int> deleted_values_;
|
||||
Cache* cache_;
|
||||
|
||||
CacheTest() : cache_(NewLRUCache(kCacheSize)) { current_ = this; }
|
||||
|
||||
~CacheTest() { delete cache_; }
|
||||
|
||||
int Lookup(int key) {
|
||||
Cache::Handle* handle = cache_->Lookup(EncodeKey(key));
|
||||
const int r = (handle == nullptr) ? -1 : DecodeValue(cache_->Value(handle));
|
||||
if (handle != nullptr) {
|
||||
cache_->Release(handle);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
void Insert(int key, int value, int charge = 1) {
|
||||
cache_->Release(cache_->Insert(EncodeKey(key), EncodeValue(value), charge,
|
||||
&CacheTest::Deleter));
|
||||
}
|
||||
|
||||
Cache::Handle* InsertAndReturnHandle(int key, int value, int charge = 1) {
|
||||
return cache_->Insert(EncodeKey(key), EncodeValue(value), charge,
|
||||
&CacheTest::Deleter);
|
||||
}
|
||||
|
||||
void Erase(int key) { cache_->Erase(EncodeKey(key)); }
|
||||
static CacheTest* current_;
|
||||
};
|
||||
CacheTest* CacheTest::current_;
|
||||
|
||||
TEST_F(CacheTest, HitAndMiss) {
|
||||
ASSERT_EQ(-1, Lookup(100));
|
||||
|
||||
Insert(100, 101);
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
ASSERT_EQ(-1, Lookup(200));
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
Insert(200, 201);
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
ASSERT_EQ(201, Lookup(200));
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
Insert(100, 102);
|
||||
ASSERT_EQ(102, Lookup(100));
|
||||
ASSERT_EQ(201, Lookup(200));
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
ASSERT_EQ(1, deleted_keys_.size());
|
||||
ASSERT_EQ(100, deleted_keys_[0]);
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, Erase) {
|
||||
Erase(200);
|
||||
ASSERT_EQ(0, deleted_keys_.size());
|
||||
|
||||
Insert(100, 101);
|
||||
Insert(200, 201);
|
||||
Erase(100);
|
||||
ASSERT_EQ(-1, Lookup(100));
|
||||
ASSERT_EQ(201, Lookup(200));
|
||||
ASSERT_EQ(1, deleted_keys_.size());
|
||||
ASSERT_EQ(100, deleted_keys_[0]);
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
|
||||
Erase(100);
|
||||
ASSERT_EQ(-1, Lookup(100));
|
||||
ASSERT_EQ(201, Lookup(200));
|
||||
ASSERT_EQ(1, deleted_keys_.size());
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, EntriesArePinned) {
|
||||
Insert(100, 101);
|
||||
Cache::Handle* h1 = cache_->Lookup(EncodeKey(100));
|
||||
ASSERT_EQ(101, DecodeValue(cache_->Value(h1)));
|
||||
|
||||
Insert(100, 102);
|
||||
Cache::Handle* h2 = cache_->Lookup(EncodeKey(100));
|
||||
ASSERT_EQ(102, DecodeValue(cache_->Value(h2)));
|
||||
ASSERT_EQ(0, deleted_keys_.size());
|
||||
|
||||
cache_->Release(h1);
|
||||
ASSERT_EQ(1, deleted_keys_.size());
|
||||
ASSERT_EQ(100, deleted_keys_[0]);
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
|
||||
Erase(100);
|
||||
ASSERT_EQ(-1, Lookup(100));
|
||||
ASSERT_EQ(1, deleted_keys_.size());
|
||||
|
||||
cache_->Release(h2);
|
||||
ASSERT_EQ(2, deleted_keys_.size());
|
||||
ASSERT_EQ(100, deleted_keys_[1]);
|
||||
ASSERT_EQ(102, deleted_values_[1]);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, EvictionPolicy) {
|
||||
Insert(100, 101);
|
||||
Insert(200, 201);
|
||||
Insert(300, 301);
|
||||
Cache::Handle* h = cache_->Lookup(EncodeKey(300));
|
||||
|
||||
// Frequently used entry must be kept around,
|
||||
// as must things that are still in use.
|
||||
for (int i = 0; i < kCacheSize + 100; i++) {
|
||||
Insert(1000 + i, 2000 + i);
|
||||
ASSERT_EQ(2000 + i, Lookup(1000 + i));
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
}
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
ASSERT_EQ(-1, Lookup(200));
|
||||
ASSERT_EQ(301, Lookup(300));
|
||||
cache_->Release(h);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, UseExceedsCacheSize) {
|
||||
// Overfill the cache, keeping handles on all inserted entries.
|
||||
std::vector<Cache::Handle*> h;
|
||||
for (int i = 0; i < kCacheSize + 100; i++) {
|
||||
h.push_back(InsertAndReturnHandle(1000 + i, 2000 + i));
|
||||
}
|
||||
|
||||
// Check that all the entries can be found in the cache.
|
||||
for (int i = 0; i < h.size(); i++) {
|
||||
ASSERT_EQ(2000 + i, Lookup(1000 + i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < h.size(); i++) {
|
||||
cache_->Release(h[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, HeavyEntries) {
|
||||
// Add a bunch of light and heavy entries and then count the combined
|
||||
// size of items still in the cache, which must be approximately the
|
||||
// same as the total capacity.
|
||||
const int kLight = 1;
|
||||
const int kHeavy = 10;
|
||||
int added = 0;
|
||||
int index = 0;
|
||||
while (added < 2 * kCacheSize) {
|
||||
const int weight = (index & 1) ? kLight : kHeavy;
|
||||
Insert(index, 1000 + index, weight);
|
||||
added += weight;
|
||||
index++;
|
||||
}
|
||||
|
||||
int cached_weight = 0;
|
||||
for (int i = 0; i < index; i++) {
|
||||
const int weight = (i & 1 ? kLight : kHeavy);
|
||||
int r = Lookup(i);
|
||||
if (r >= 0) {
|
||||
cached_weight += weight;
|
||||
ASSERT_EQ(1000 + i, r);
|
||||
}
|
||||
}
|
||||
ASSERT_LE(cached_weight, kCacheSize + kCacheSize / 10);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, NewId) {
|
||||
uint64_t a = cache_->NewId();
|
||||
uint64_t b = cache_->NewId();
|
||||
ASSERT_NE(a, b);
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, Prune) {
|
||||
Insert(1, 100);
|
||||
Insert(2, 200);
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(EncodeKey(1));
|
||||
ASSERT_TRUE(handle);
|
||||
cache_->Prune();
|
||||
cache_->Release(handle);
|
||||
|
||||
ASSERT_EQ(100, Lookup(1));
|
||||
ASSERT_EQ(-1, Lookup(2));
|
||||
}
|
||||
|
||||
TEST_F(CacheTest, ZeroSizeCache) {
|
||||
delete cache_;
|
||||
cache_ = NewLRUCache(0);
|
||||
|
||||
Insert(1, 100);
|
||||
ASSERT_EQ(-1, Lookup(1));
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
void PutFixed32(std::string* dst, uint32_t value) {
|
||||
char buf[sizeof(value)];
|
||||
EncodeFixed32(buf, value);
|
||||
dst->append(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void PutFixed64(std::string* dst, uint64_t value) {
|
||||
char buf[sizeof(value)];
|
||||
EncodeFixed64(buf, value);
|
||||
dst->append(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
char* EncodeVarint32(char* dst, uint32_t v) {
|
||||
// Operate on characters as unsigneds
|
||||
uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
|
||||
static const int B = 128;
|
||||
if (v < (1 << 7)) {
|
||||
*(ptr++) = v;
|
||||
} else if (v < (1 << 14)) {
|
||||
*(ptr++) = v | B;
|
||||
*(ptr++) = v >> 7;
|
||||
} else if (v < (1 << 21)) {
|
||||
*(ptr++) = v | B;
|
||||
*(ptr++) = (v >> 7) | B;
|
||||
*(ptr++) = v >> 14;
|
||||
} else if (v < (1 << 28)) {
|
||||
*(ptr++) = v | B;
|
||||
*(ptr++) = (v >> 7) | B;
|
||||
*(ptr++) = (v >> 14) | B;
|
||||
*(ptr++) = v >> 21;
|
||||
} else {
|
||||
*(ptr++) = v | B;
|
||||
*(ptr++) = (v >> 7) | B;
|
||||
*(ptr++) = (v >> 14) | B;
|
||||
*(ptr++) = (v >> 21) | B;
|
||||
*(ptr++) = v >> 28;
|
||||
}
|
||||
return reinterpret_cast<char*>(ptr);
|
||||
}
|
||||
|
||||
void PutVarint32(std::string* dst, uint32_t v) {
|
||||
char buf[5];
|
||||
char* ptr = EncodeVarint32(buf, v);
|
||||
dst->append(buf, ptr - buf);
|
||||
}
|
||||
|
||||
char* EncodeVarint64(char* dst, uint64_t v) {
|
||||
static const int B = 128;
|
||||
uint8_t* ptr = reinterpret_cast<uint8_t*>(dst);
|
||||
while (v >= B) {
|
||||
*(ptr++) = v | B;
|
||||
v >>= 7;
|
||||
}
|
||||
*(ptr++) = static_cast<uint8_t>(v);
|
||||
return reinterpret_cast<char*>(ptr);
|
||||
}
|
||||
|
||||
void PutVarint64(std::string* dst, uint64_t v) {
|
||||
char buf[10];
|
||||
char* ptr = EncodeVarint64(buf, v);
|
||||
dst->append(buf, ptr - buf);
|
||||
}
|
||||
|
||||
void PutLengthPrefixedSlice(std::string* dst, const Slice& value) {
|
||||
PutVarint32(dst, value.size());
|
||||
dst->append(value.data(), value.size());
|
||||
}
|
||||
|
||||
int VarintLength(uint64_t v) {
|
||||
int len = 1;
|
||||
while (v >= 128) {
|
||||
v >>= 7;
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
const char* GetVarint32PtrFallback(const char* p, const char* limit,
|
||||
uint32_t* value) {
|
||||
uint32_t result = 0;
|
||||
for (uint32_t shift = 0; shift <= 28 && p < limit; shift += 7) {
|
||||
uint32_t byte = *(reinterpret_cast<const uint8_t*>(p));
|
||||
p++;
|
||||
if (byte & 128) {
|
||||
// More bytes are present
|
||||
result |= ((byte & 127) << shift);
|
||||
} else {
|
||||
result |= (byte << shift);
|
||||
*value = result;
|
||||
return reinterpret_cast<const char*>(p);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool GetVarint32(Slice* input, uint32_t* value) {
|
||||
const char* p = input->data();
|
||||
const char* limit = p + input->size();
|
||||
const char* q = GetVarint32Ptr(p, limit, value);
|
||||
if (q == nullptr) {
|
||||
return false;
|
||||
} else {
|
||||
*input = Slice(q, limit - q);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* value) {
|
||||
uint64_t result = 0;
|
||||
for (uint32_t shift = 0; shift <= 63 && p < limit; shift += 7) {
|
||||
uint64_t byte = *(reinterpret_cast<const uint8_t*>(p));
|
||||
p++;
|
||||
if (byte & 128) {
|
||||
// More bytes are present
|
||||
result |= ((byte & 127) << shift);
|
||||
} else {
|
||||
result |= (byte << shift);
|
||||
*value = result;
|
||||
return reinterpret_cast<const char*>(p);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool GetVarint64(Slice* input, uint64_t* value) {
|
||||
const char* p = input->data();
|
||||
const char* limit = p + input->size();
|
||||
const char* q = GetVarint64Ptr(p, limit, value);
|
||||
if (q == nullptr) {
|
||||
return false;
|
||||
} else {
|
||||
*input = Slice(q, limit - q);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const char* GetLengthPrefixedSlice(const char* p, const char* limit,
|
||||
Slice* result) {
|
||||
uint32_t len;
|
||||
p = GetVarint32Ptr(p, limit, &len);
|
||||
if (p == nullptr) return nullptr;
|
||||
if (p + len > limit) return nullptr;
|
||||
*result = Slice(p, len);
|
||||
return p + len;
|
||||
}
|
||||
|
||||
bool GetLengthPrefixedSlice(Slice* input, Slice* result) {
|
||||
uint32_t len;
|
||||
if (GetVarint32(input, &len) && input->size() >= len) {
|
||||
*result = Slice(input->data(), len);
|
||||
input->remove_prefix(len);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// Endian-neutral encoding:
|
||||
// * Fixed-length numbers are encoded with least-significant byte first
|
||||
// * In addition we support variable length "varint" encoding
|
||||
// * Strings are encoded prefixed by their length in varint format
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_CODING_H_
|
||||
#define STORAGE_LEVELDB_UTIL_CODING_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "leveldb/slice.h"
|
||||
#include "port/port.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Standard Put... routines append to a string
|
||||
void PutFixed32(std::string* dst, uint32_t value);
|
||||
void PutFixed64(std::string* dst, uint64_t value);
|
||||
void PutVarint32(std::string* dst, uint32_t value);
|
||||
void PutVarint64(std::string* dst, uint64_t value);
|
||||
void PutLengthPrefixedSlice(std::string* dst, const Slice& value);
|
||||
|
||||
// Standard Get... routines parse a value from the beginning of a Slice
|
||||
// and advance the slice past the parsed value.
|
||||
bool GetVarint32(Slice* input, uint32_t* value);
|
||||
bool GetVarint64(Slice* input, uint64_t* value);
|
||||
bool GetLengthPrefixedSlice(Slice* input, Slice* result);
|
||||
|
||||
// Pointer-based variants of GetVarint... These either store a value
|
||||
// in *v and return a pointer just past the parsed value, or return
|
||||
// nullptr on error. These routines only look at bytes in the range
|
||||
// [p..limit-1]
|
||||
const char* GetVarint32Ptr(const char* p, const char* limit, uint32_t* v);
|
||||
const char* GetVarint64Ptr(const char* p, const char* limit, uint64_t* v);
|
||||
|
||||
// Returns the length of the varint32 or varint64 encoding of "v"
|
||||
int VarintLength(uint64_t v);
|
||||
|
||||
// Lower-level versions of Put... that write directly into a character buffer
|
||||
// and return a pointer just past the last byte written.
|
||||
// REQUIRES: dst has enough space for the value being written
|
||||
char* EncodeVarint32(char* dst, uint32_t value);
|
||||
char* EncodeVarint64(char* dst, uint64_t value);
|
||||
|
||||
// Lower-level versions of Put... that write directly into a character buffer
|
||||
// REQUIRES: dst has enough space for the value being written
|
||||
|
||||
inline void EncodeFixed32(char* dst, uint32_t value) {
|
||||
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
||||
|
||||
// Recent clang and gcc optimize this to a single mov / str instruction.
|
||||
buffer[0] = static_cast<uint8_t>(value);
|
||||
buffer[1] = static_cast<uint8_t>(value >> 8);
|
||||
buffer[2] = static_cast<uint8_t>(value >> 16);
|
||||
buffer[3] = static_cast<uint8_t>(value >> 24);
|
||||
}
|
||||
|
||||
inline void EncodeFixed64(char* dst, uint64_t value) {
|
||||
uint8_t* const buffer = reinterpret_cast<uint8_t*>(dst);
|
||||
|
||||
// Recent clang and gcc optimize this to a single mov / str instruction.
|
||||
buffer[0] = static_cast<uint8_t>(value);
|
||||
buffer[1] = static_cast<uint8_t>(value >> 8);
|
||||
buffer[2] = static_cast<uint8_t>(value >> 16);
|
||||
buffer[3] = static_cast<uint8_t>(value >> 24);
|
||||
buffer[4] = static_cast<uint8_t>(value >> 32);
|
||||
buffer[5] = static_cast<uint8_t>(value >> 40);
|
||||
buffer[6] = static_cast<uint8_t>(value >> 48);
|
||||
buffer[7] = static_cast<uint8_t>(value >> 56);
|
||||
}
|
||||
|
||||
// Lower-level versions of Get... that read directly from a character buffer
|
||||
// without any bounds checking.
|
||||
|
||||
inline uint32_t DecodeFixed32(const char* ptr) {
|
||||
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
||||
|
||||
// Recent clang and gcc optimize this to a single mov / ldr instruction.
|
||||
return (static_cast<uint32_t>(buffer[0])) |
|
||||
(static_cast<uint32_t>(buffer[1]) << 8) |
|
||||
(static_cast<uint32_t>(buffer[2]) << 16) |
|
||||
(static_cast<uint32_t>(buffer[3]) << 24);
|
||||
}
|
||||
|
||||
inline uint64_t DecodeFixed64(const char* ptr) {
|
||||
const uint8_t* const buffer = reinterpret_cast<const uint8_t*>(ptr);
|
||||
|
||||
// Recent clang and gcc optimize this to a single mov / ldr instruction.
|
||||
return (static_cast<uint64_t>(buffer[0])) |
|
||||
(static_cast<uint64_t>(buffer[1]) << 8) |
|
||||
(static_cast<uint64_t>(buffer[2]) << 16) |
|
||||
(static_cast<uint64_t>(buffer[3]) << 24) |
|
||||
(static_cast<uint64_t>(buffer[4]) << 32) |
|
||||
(static_cast<uint64_t>(buffer[5]) << 40) |
|
||||
(static_cast<uint64_t>(buffer[6]) << 48) |
|
||||
(static_cast<uint64_t>(buffer[7]) << 56);
|
||||
}
|
||||
|
||||
// Internal routine for use by fallback path of GetVarint32Ptr
|
||||
const char* GetVarint32PtrFallback(const char* p, const char* limit,
|
||||
uint32_t* value);
|
||||
inline const char* GetVarint32Ptr(const char* p, const char* limit,
|
||||
uint32_t* value) {
|
||||
if (p < limit) {
|
||||
uint32_t result = *(reinterpret_cast<const uint8_t*>(p));
|
||||
if ((result & 128) == 0) {
|
||||
*value = result;
|
||||
return p + 1;
|
||||
}
|
||||
}
|
||||
return GetVarint32PtrFallback(p, limit, value);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_CODING_H_
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/coding.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
TEST(Coding, Fixed32) {
|
||||
std::string s;
|
||||
for (uint32_t v = 0; v < 100000; v++) {
|
||||
PutFixed32(&s, v);
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
for (uint32_t v = 0; v < 100000; v++) {
|
||||
uint32_t actual = DecodeFixed32(p);
|
||||
ASSERT_EQ(v, actual);
|
||||
p += sizeof(uint32_t);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Coding, Fixed64) {
|
||||
std::string s;
|
||||
for (int power = 0; power <= 63; power++) {
|
||||
uint64_t v = static_cast<uint64_t>(1) << power;
|
||||
PutFixed64(&s, v - 1);
|
||||
PutFixed64(&s, v + 0);
|
||||
PutFixed64(&s, v + 1);
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
for (int power = 0; power <= 63; power++) {
|
||||
uint64_t v = static_cast<uint64_t>(1) << power;
|
||||
uint64_t actual;
|
||||
actual = DecodeFixed64(p);
|
||||
ASSERT_EQ(v - 1, actual);
|
||||
p += sizeof(uint64_t);
|
||||
|
||||
actual = DecodeFixed64(p);
|
||||
ASSERT_EQ(v + 0, actual);
|
||||
p += sizeof(uint64_t);
|
||||
|
||||
actual = DecodeFixed64(p);
|
||||
ASSERT_EQ(v + 1, actual);
|
||||
p += sizeof(uint64_t);
|
||||
}
|
||||
}
|
||||
|
||||
// Test that encoding routines generate little-endian encodings
|
||||
TEST(Coding, EncodingOutput) {
|
||||
std::string dst;
|
||||
PutFixed32(&dst, 0x04030201);
|
||||
ASSERT_EQ(4, dst.size());
|
||||
ASSERT_EQ(0x01, static_cast<int>(dst[0]));
|
||||
ASSERT_EQ(0x02, static_cast<int>(dst[1]));
|
||||
ASSERT_EQ(0x03, static_cast<int>(dst[2]));
|
||||
ASSERT_EQ(0x04, static_cast<int>(dst[3]));
|
||||
|
||||
dst.clear();
|
||||
PutFixed64(&dst, 0x0807060504030201ull);
|
||||
ASSERT_EQ(8, dst.size());
|
||||
ASSERT_EQ(0x01, static_cast<int>(dst[0]));
|
||||
ASSERT_EQ(0x02, static_cast<int>(dst[1]));
|
||||
ASSERT_EQ(0x03, static_cast<int>(dst[2]));
|
||||
ASSERT_EQ(0x04, static_cast<int>(dst[3]));
|
||||
ASSERT_EQ(0x05, static_cast<int>(dst[4]));
|
||||
ASSERT_EQ(0x06, static_cast<int>(dst[5]));
|
||||
ASSERT_EQ(0x07, static_cast<int>(dst[6]));
|
||||
ASSERT_EQ(0x08, static_cast<int>(dst[7]));
|
||||
}
|
||||
|
||||
TEST(Coding, Varint32) {
|
||||
std::string s;
|
||||
for (uint32_t i = 0; i < (32 * 32); i++) {
|
||||
uint32_t v = (i / 32) << (i % 32);
|
||||
PutVarint32(&s, v);
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
const char* limit = p + s.size();
|
||||
for (uint32_t i = 0; i < (32 * 32); i++) {
|
||||
uint32_t expected = (i / 32) << (i % 32);
|
||||
uint32_t actual;
|
||||
const char* start = p;
|
||||
p = GetVarint32Ptr(p, limit, &actual);
|
||||
ASSERT_TRUE(p != nullptr);
|
||||
ASSERT_EQ(expected, actual);
|
||||
ASSERT_EQ(VarintLength(actual), p - start);
|
||||
}
|
||||
ASSERT_EQ(p, s.data() + s.size());
|
||||
}
|
||||
|
||||
TEST(Coding, Varint64) {
|
||||
// Construct the list of values to check
|
||||
std::vector<uint64_t> values;
|
||||
// Some special values
|
||||
values.push_back(0);
|
||||
values.push_back(100);
|
||||
values.push_back(~static_cast<uint64_t>(0));
|
||||
values.push_back(~static_cast<uint64_t>(0) - 1);
|
||||
for (uint32_t k = 0; k < 64; k++) {
|
||||
// Test values near powers of two
|
||||
const uint64_t power = 1ull << k;
|
||||
values.push_back(power);
|
||||
values.push_back(power - 1);
|
||||
values.push_back(power + 1);
|
||||
}
|
||||
|
||||
std::string s;
|
||||
for (size_t i = 0; i < values.size(); i++) {
|
||||
PutVarint64(&s, values[i]);
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
const char* limit = p + s.size();
|
||||
for (size_t i = 0; i < values.size(); i++) {
|
||||
ASSERT_TRUE(p < limit);
|
||||
uint64_t actual;
|
||||
const char* start = p;
|
||||
p = GetVarint64Ptr(p, limit, &actual);
|
||||
ASSERT_TRUE(p != nullptr);
|
||||
ASSERT_EQ(values[i], actual);
|
||||
ASSERT_EQ(VarintLength(actual), p - start);
|
||||
}
|
||||
ASSERT_EQ(p, limit);
|
||||
}
|
||||
|
||||
TEST(Coding, Varint32Overflow) {
|
||||
uint32_t result;
|
||||
std::string input("\x81\x82\x83\x84\x85\x11");
|
||||
ASSERT_TRUE(GetVarint32Ptr(input.data(), input.data() + input.size(),
|
||||
&result) == nullptr);
|
||||
}
|
||||
|
||||
TEST(Coding, Varint32Truncation) {
|
||||
uint32_t large_value = (1u << 31) + 100;
|
||||
std::string s;
|
||||
PutVarint32(&s, large_value);
|
||||
uint32_t result;
|
||||
for (size_t len = 0; len < s.size() - 1; len++) {
|
||||
ASSERT_TRUE(GetVarint32Ptr(s.data(), s.data() + len, &result) == nullptr);
|
||||
}
|
||||
ASSERT_TRUE(GetVarint32Ptr(s.data(), s.data() + s.size(), &result) !=
|
||||
nullptr);
|
||||
ASSERT_EQ(large_value, result);
|
||||
}
|
||||
|
||||
TEST(Coding, Varint64Overflow) {
|
||||
uint64_t result;
|
||||
std::string input("\x81\x82\x83\x84\x85\x81\x82\x83\x84\x85\x11");
|
||||
ASSERT_TRUE(GetVarint64Ptr(input.data(), input.data() + input.size(),
|
||||
&result) == nullptr);
|
||||
}
|
||||
|
||||
TEST(Coding, Varint64Truncation) {
|
||||
uint64_t large_value = (1ull << 63) + 100ull;
|
||||
std::string s;
|
||||
PutVarint64(&s, large_value);
|
||||
uint64_t result;
|
||||
for (size_t len = 0; len < s.size() - 1; len++) {
|
||||
ASSERT_TRUE(GetVarint64Ptr(s.data(), s.data() + len, &result) == nullptr);
|
||||
}
|
||||
ASSERT_TRUE(GetVarint64Ptr(s.data(), s.data() + s.size(), &result) !=
|
||||
nullptr);
|
||||
ASSERT_EQ(large_value, result);
|
||||
}
|
||||
|
||||
TEST(Coding, Strings) {
|
||||
std::string s;
|
||||
PutLengthPrefixedSlice(&s, Slice(""));
|
||||
PutLengthPrefixedSlice(&s, Slice("foo"));
|
||||
PutLengthPrefixedSlice(&s, Slice("bar"));
|
||||
PutLengthPrefixedSlice(&s, Slice(std::string(200, 'x')));
|
||||
|
||||
Slice input(s);
|
||||
Slice v;
|
||||
ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v));
|
||||
ASSERT_EQ("", v.ToString());
|
||||
ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v));
|
||||
ASSERT_EQ("foo", v.ToString());
|
||||
ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v));
|
||||
ASSERT_EQ("bar", v.ToString());
|
||||
ASSERT_TRUE(GetLengthPrefixedSlice(&input, &v));
|
||||
ASSERT_EQ(std::string(200, 'x'), v.ToString());
|
||||
ASSERT_EQ("", input.ToString());
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/comparator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "leveldb/slice.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/no_destructor.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
Comparator::~Comparator() = default;
|
||||
|
||||
namespace {
|
||||
class BytewiseComparatorImpl : public Comparator {
|
||||
public:
|
||||
BytewiseComparatorImpl() = default;
|
||||
|
||||
const char* Name() const override { return "leveldb.BytewiseComparator"; }
|
||||
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
return a.compare(b);
|
||||
}
|
||||
|
||||
void FindShortestSeparator(std::string* start,
|
||||
const Slice& limit) const override {
|
||||
// Find length of common prefix
|
||||
size_t min_length = std::min(start->size(), limit.size());
|
||||
size_t diff_index = 0;
|
||||
while ((diff_index < min_length) &&
|
||||
((*start)[diff_index] == limit[diff_index])) {
|
||||
diff_index++;
|
||||
}
|
||||
|
||||
if (diff_index >= min_length) {
|
||||
// Do not shorten if one string is a prefix of the other
|
||||
} else {
|
||||
uint8_t diff_byte = static_cast<uint8_t>((*start)[diff_index]);
|
||||
if (diff_byte < static_cast<uint8_t>(0xff) &&
|
||||
diff_byte + 1 < static_cast<uint8_t>(limit[diff_index])) {
|
||||
(*start)[diff_index]++;
|
||||
start->resize(diff_index + 1);
|
||||
assert(Compare(*start, limit) < 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FindShortSuccessor(std::string* key) const override {
|
||||
// Find first character that can be incremented
|
||||
size_t n = key->size();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const uint8_t byte = (*key)[i];
|
||||
if (byte != static_cast<uint8_t>(0xff)) {
|
||||
(*key)[i] = byte + 1;
|
||||
key->resize(i + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// *key is a run of 0xffs. Leave it alone.
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
const Comparator* BytewiseComparator() {
|
||||
static NoDestructor<BytewiseComparatorImpl> singleton;
|
||||
return singleton.get();
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,380 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// A portable implementation of crc32c.
|
||||
|
||||
#include "util/crc32c.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace leveldb {
|
||||
namespace crc32c {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kByteExtensionTable[256] = {
|
||||
0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c,
|
||||
0x26a1e7e8, 0xd4ca64eb, 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b,
|
||||
0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, 0x105ec76f, 0xe235446c,
|
||||
0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,
|
||||
0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc,
|
||||
0xbc267848, 0x4e4dfb4b, 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a,
|
||||
0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, 0xaa64d611, 0x580f5512,
|
||||
0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,
|
||||
0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad,
|
||||
0x1642ae59, 0xe4292d5a, 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a,
|
||||
0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, 0x417b1dbc, 0xb3109ebf,
|
||||
0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,
|
||||
0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f,
|
||||
0xed03a29b, 0x1f682198, 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927,
|
||||
0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, 0xdbfc821c, 0x2997011f,
|
||||
0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,
|
||||
0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e,
|
||||
0x4767748a, 0xb50cf789, 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859,
|
||||
0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, 0x7198540d, 0x83f3d70e,
|
||||
0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,
|
||||
0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de,
|
||||
0xdde0eb2a, 0x2f8b6829, 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c,
|
||||
0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, 0x082f63b7, 0xfa44e0b4,
|
||||
0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,
|
||||
0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b,
|
||||
0xb4091bff, 0x466298fc, 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c,
|
||||
0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, 0xa24bb5a6, 0x502036a5,
|
||||
0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,
|
||||
0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975,
|
||||
0x0e330a81, 0xfc588982, 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d,
|
||||
0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, 0x38cc2a06, 0xcaa7a905,
|
||||
0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,
|
||||
0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8,
|
||||
0xe52cc12c, 0x1747422f, 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff,
|
||||
0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, 0xd3d3e1ab, 0x21b862a8,
|
||||
0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,
|
||||
0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78,
|
||||
0x7fab5e8c, 0x8dc0dd8f, 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee,
|
||||
0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, 0x69e9f0d5, 0x9b8273d6,
|
||||
0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,
|
||||
0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69,
|
||||
0xd5cf889d, 0x27a40b9e, 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e,
|
||||
0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351};
|
||||
|
||||
const uint32_t kStrideExtensionTable0[256] = {
|
||||
0x00000000, 0x30d23865, 0x61a470ca, 0x517648af, 0xc348e194, 0xf39ad9f1,
|
||||
0xa2ec915e, 0x923ea93b, 0x837db5d9, 0xb3af8dbc, 0xe2d9c513, 0xd20bfd76,
|
||||
0x4035544d, 0x70e76c28, 0x21912487, 0x11431ce2, 0x03171d43, 0x33c52526,
|
||||
0x62b36d89, 0x526155ec, 0xc05ffcd7, 0xf08dc4b2, 0xa1fb8c1d, 0x9129b478,
|
||||
0x806aa89a, 0xb0b890ff, 0xe1ced850, 0xd11ce035, 0x4322490e, 0x73f0716b,
|
||||
0x228639c4, 0x125401a1, 0x062e3a86, 0x36fc02e3, 0x678a4a4c, 0x57587229,
|
||||
0xc566db12, 0xf5b4e377, 0xa4c2abd8, 0x941093bd, 0x85538f5f, 0xb581b73a,
|
||||
0xe4f7ff95, 0xd425c7f0, 0x461b6ecb, 0x76c956ae, 0x27bf1e01, 0x176d2664,
|
||||
0x053927c5, 0x35eb1fa0, 0x649d570f, 0x544f6f6a, 0xc671c651, 0xf6a3fe34,
|
||||
0xa7d5b69b, 0x97078efe, 0x8644921c, 0xb696aa79, 0xe7e0e2d6, 0xd732dab3,
|
||||
0x450c7388, 0x75de4bed, 0x24a80342, 0x147a3b27, 0x0c5c750c, 0x3c8e4d69,
|
||||
0x6df805c6, 0x5d2a3da3, 0xcf149498, 0xffc6acfd, 0xaeb0e452, 0x9e62dc37,
|
||||
0x8f21c0d5, 0xbff3f8b0, 0xee85b01f, 0xde57887a, 0x4c692141, 0x7cbb1924,
|
||||
0x2dcd518b, 0x1d1f69ee, 0x0f4b684f, 0x3f99502a, 0x6eef1885, 0x5e3d20e0,
|
||||
0xcc0389db, 0xfcd1b1be, 0xada7f911, 0x9d75c174, 0x8c36dd96, 0xbce4e5f3,
|
||||
0xed92ad5c, 0xdd409539, 0x4f7e3c02, 0x7fac0467, 0x2eda4cc8, 0x1e0874ad,
|
||||
0x0a724f8a, 0x3aa077ef, 0x6bd63f40, 0x5b040725, 0xc93aae1e, 0xf9e8967b,
|
||||
0xa89eded4, 0x984ce6b1, 0x890ffa53, 0xb9ddc236, 0xe8ab8a99, 0xd879b2fc,
|
||||
0x4a471bc7, 0x7a9523a2, 0x2be36b0d, 0x1b315368, 0x096552c9, 0x39b76aac,
|
||||
0x68c12203, 0x58131a66, 0xca2db35d, 0xfaff8b38, 0xab89c397, 0x9b5bfbf2,
|
||||
0x8a18e710, 0xbacadf75, 0xebbc97da, 0xdb6eafbf, 0x49500684, 0x79823ee1,
|
||||
0x28f4764e, 0x18264e2b, 0x18b8ea18, 0x286ad27d, 0x791c9ad2, 0x49cea2b7,
|
||||
0xdbf00b8c, 0xeb2233e9, 0xba547b46, 0x8a864323, 0x9bc55fc1, 0xab1767a4,
|
||||
0xfa612f0b, 0xcab3176e, 0x588dbe55, 0x685f8630, 0x3929ce9f, 0x09fbf6fa,
|
||||
0x1baff75b, 0x2b7dcf3e, 0x7a0b8791, 0x4ad9bff4, 0xd8e716cf, 0xe8352eaa,
|
||||
0xb9436605, 0x89915e60, 0x98d24282, 0xa8007ae7, 0xf9763248, 0xc9a40a2d,
|
||||
0x5b9aa316, 0x6b489b73, 0x3a3ed3dc, 0x0aecebb9, 0x1e96d09e, 0x2e44e8fb,
|
||||
0x7f32a054, 0x4fe09831, 0xddde310a, 0xed0c096f, 0xbc7a41c0, 0x8ca879a5,
|
||||
0x9deb6547, 0xad395d22, 0xfc4f158d, 0xcc9d2de8, 0x5ea384d3, 0x6e71bcb6,
|
||||
0x3f07f419, 0x0fd5cc7c, 0x1d81cddd, 0x2d53f5b8, 0x7c25bd17, 0x4cf78572,
|
||||
0xdec92c49, 0xee1b142c, 0xbf6d5c83, 0x8fbf64e6, 0x9efc7804, 0xae2e4061,
|
||||
0xff5808ce, 0xcf8a30ab, 0x5db49990, 0x6d66a1f5, 0x3c10e95a, 0x0cc2d13f,
|
||||
0x14e49f14, 0x2436a771, 0x7540efde, 0x4592d7bb, 0xd7ac7e80, 0xe77e46e5,
|
||||
0xb6080e4a, 0x86da362f, 0x97992acd, 0xa74b12a8, 0xf63d5a07, 0xc6ef6262,
|
||||
0x54d1cb59, 0x6403f33c, 0x3575bb93, 0x05a783f6, 0x17f38257, 0x2721ba32,
|
||||
0x7657f29d, 0x4685caf8, 0xd4bb63c3, 0xe4695ba6, 0xb51f1309, 0x85cd2b6c,
|
||||
0x948e378e, 0xa45c0feb, 0xf52a4744, 0xc5f87f21, 0x57c6d61a, 0x6714ee7f,
|
||||
0x3662a6d0, 0x06b09eb5, 0x12caa592, 0x22189df7, 0x736ed558, 0x43bced3d,
|
||||
0xd1824406, 0xe1507c63, 0xb02634cc, 0x80f40ca9, 0x91b7104b, 0xa165282e,
|
||||
0xf0136081, 0xc0c158e4, 0x52fff1df, 0x622dc9ba, 0x335b8115, 0x0389b970,
|
||||
0x11ddb8d1, 0x210f80b4, 0x7079c81b, 0x40abf07e, 0xd2955945, 0xe2476120,
|
||||
0xb331298f, 0x83e311ea, 0x92a00d08, 0xa272356d, 0xf3047dc2, 0xc3d645a7,
|
||||
0x51e8ec9c, 0x613ad4f9, 0x304c9c56, 0x009ea433};
|
||||
|
||||
const uint32_t kStrideExtensionTable1[256] = {
|
||||
0x00000000, 0x54075546, 0xa80eaa8c, 0xfc09ffca, 0x55f123e9, 0x01f676af,
|
||||
0xfdff8965, 0xa9f8dc23, 0xabe247d2, 0xffe51294, 0x03eced5e, 0x57ebb818,
|
||||
0xfe13643b, 0xaa14317d, 0x561dceb7, 0x021a9bf1, 0x5228f955, 0x062fac13,
|
||||
0xfa2653d9, 0xae21069f, 0x07d9dabc, 0x53de8ffa, 0xafd77030, 0xfbd02576,
|
||||
0xf9cabe87, 0xadcdebc1, 0x51c4140b, 0x05c3414d, 0xac3b9d6e, 0xf83cc828,
|
||||
0x043537e2, 0x503262a4, 0xa451f2aa, 0xf056a7ec, 0x0c5f5826, 0x58580d60,
|
||||
0xf1a0d143, 0xa5a78405, 0x59ae7bcf, 0x0da92e89, 0x0fb3b578, 0x5bb4e03e,
|
||||
0xa7bd1ff4, 0xf3ba4ab2, 0x5a429691, 0x0e45c3d7, 0xf24c3c1d, 0xa64b695b,
|
||||
0xf6790bff, 0xa27e5eb9, 0x5e77a173, 0x0a70f435, 0xa3882816, 0xf78f7d50,
|
||||
0x0b86829a, 0x5f81d7dc, 0x5d9b4c2d, 0x099c196b, 0xf595e6a1, 0xa192b3e7,
|
||||
0x086a6fc4, 0x5c6d3a82, 0xa064c548, 0xf463900e, 0x4d4f93a5, 0x1948c6e3,
|
||||
0xe5413929, 0xb1466c6f, 0x18beb04c, 0x4cb9e50a, 0xb0b01ac0, 0xe4b74f86,
|
||||
0xe6add477, 0xb2aa8131, 0x4ea37efb, 0x1aa42bbd, 0xb35cf79e, 0xe75ba2d8,
|
||||
0x1b525d12, 0x4f550854, 0x1f676af0, 0x4b603fb6, 0xb769c07c, 0xe36e953a,
|
||||
0x4a964919, 0x1e911c5f, 0xe298e395, 0xb69fb6d3, 0xb4852d22, 0xe0827864,
|
||||
0x1c8b87ae, 0x488cd2e8, 0xe1740ecb, 0xb5735b8d, 0x497aa447, 0x1d7df101,
|
||||
0xe91e610f, 0xbd193449, 0x4110cb83, 0x15179ec5, 0xbcef42e6, 0xe8e817a0,
|
||||
0x14e1e86a, 0x40e6bd2c, 0x42fc26dd, 0x16fb739b, 0xeaf28c51, 0xbef5d917,
|
||||
0x170d0534, 0x430a5072, 0xbf03afb8, 0xeb04fafe, 0xbb36985a, 0xef31cd1c,
|
||||
0x133832d6, 0x473f6790, 0xeec7bbb3, 0xbac0eef5, 0x46c9113f, 0x12ce4479,
|
||||
0x10d4df88, 0x44d38ace, 0xb8da7504, 0xecdd2042, 0x4525fc61, 0x1122a927,
|
||||
0xed2b56ed, 0xb92c03ab, 0x9a9f274a, 0xce98720c, 0x32918dc6, 0x6696d880,
|
||||
0xcf6e04a3, 0x9b6951e5, 0x6760ae2f, 0x3367fb69, 0x317d6098, 0x657a35de,
|
||||
0x9973ca14, 0xcd749f52, 0x648c4371, 0x308b1637, 0xcc82e9fd, 0x9885bcbb,
|
||||
0xc8b7de1f, 0x9cb08b59, 0x60b97493, 0x34be21d5, 0x9d46fdf6, 0xc941a8b0,
|
||||
0x3548577a, 0x614f023c, 0x635599cd, 0x3752cc8b, 0xcb5b3341, 0x9f5c6607,
|
||||
0x36a4ba24, 0x62a3ef62, 0x9eaa10a8, 0xcaad45ee, 0x3eced5e0, 0x6ac980a6,
|
||||
0x96c07f6c, 0xc2c72a2a, 0x6b3ff609, 0x3f38a34f, 0xc3315c85, 0x973609c3,
|
||||
0x952c9232, 0xc12bc774, 0x3d2238be, 0x69256df8, 0xc0ddb1db, 0x94dae49d,
|
||||
0x68d31b57, 0x3cd44e11, 0x6ce62cb5, 0x38e179f3, 0xc4e88639, 0x90efd37f,
|
||||
0x39170f5c, 0x6d105a1a, 0x9119a5d0, 0xc51ef096, 0xc7046b67, 0x93033e21,
|
||||
0x6f0ac1eb, 0x3b0d94ad, 0x92f5488e, 0xc6f21dc8, 0x3afbe202, 0x6efcb744,
|
||||
0xd7d0b4ef, 0x83d7e1a9, 0x7fde1e63, 0x2bd94b25, 0x82219706, 0xd626c240,
|
||||
0x2a2f3d8a, 0x7e2868cc, 0x7c32f33d, 0x2835a67b, 0xd43c59b1, 0x803b0cf7,
|
||||
0x29c3d0d4, 0x7dc48592, 0x81cd7a58, 0xd5ca2f1e, 0x85f84dba, 0xd1ff18fc,
|
||||
0x2df6e736, 0x79f1b270, 0xd0096e53, 0x840e3b15, 0x7807c4df, 0x2c009199,
|
||||
0x2e1a0a68, 0x7a1d5f2e, 0x8614a0e4, 0xd213f5a2, 0x7beb2981, 0x2fec7cc7,
|
||||
0xd3e5830d, 0x87e2d64b, 0x73814645, 0x27861303, 0xdb8fecc9, 0x8f88b98f,
|
||||
0x267065ac, 0x727730ea, 0x8e7ecf20, 0xda799a66, 0xd8630197, 0x8c6454d1,
|
||||
0x706dab1b, 0x246afe5d, 0x8d92227e, 0xd9957738, 0x259c88f2, 0x719bddb4,
|
||||
0x21a9bf10, 0x75aeea56, 0x89a7159c, 0xdda040da, 0x74589cf9, 0x205fc9bf,
|
||||
0xdc563675, 0x88516333, 0x8a4bf8c2, 0xde4cad84, 0x2245524e, 0x76420708,
|
||||
0xdfbadb2b, 0x8bbd8e6d, 0x77b471a7, 0x23b324e1};
|
||||
|
||||
const uint32_t kStrideExtensionTable2[256] = {
|
||||
0x00000000, 0x678efd01, 0xcf1dfa02, 0xa8930703, 0x9bd782f5, 0xfc597ff4,
|
||||
0x54ca78f7, 0x334485f6, 0x3243731b, 0x55cd8e1a, 0xfd5e8919, 0x9ad07418,
|
||||
0xa994f1ee, 0xce1a0cef, 0x66890bec, 0x0107f6ed, 0x6486e636, 0x03081b37,
|
||||
0xab9b1c34, 0xcc15e135, 0xff5164c3, 0x98df99c2, 0x304c9ec1, 0x57c263c0,
|
||||
0x56c5952d, 0x314b682c, 0x99d86f2f, 0xfe56922e, 0xcd1217d8, 0xaa9cead9,
|
||||
0x020fedda, 0x658110db, 0xc90dcc6c, 0xae83316d, 0x0610366e, 0x619ecb6f,
|
||||
0x52da4e99, 0x3554b398, 0x9dc7b49b, 0xfa49499a, 0xfb4ebf77, 0x9cc04276,
|
||||
0x34534575, 0x53ddb874, 0x60993d82, 0x0717c083, 0xaf84c780, 0xc80a3a81,
|
||||
0xad8b2a5a, 0xca05d75b, 0x6296d058, 0x05182d59, 0x365ca8af, 0x51d255ae,
|
||||
0xf94152ad, 0x9ecfafac, 0x9fc85941, 0xf846a440, 0x50d5a343, 0x375b5e42,
|
||||
0x041fdbb4, 0x639126b5, 0xcb0221b6, 0xac8cdcb7, 0x97f7ee29, 0xf0791328,
|
||||
0x58ea142b, 0x3f64e92a, 0x0c206cdc, 0x6bae91dd, 0xc33d96de, 0xa4b36bdf,
|
||||
0xa5b49d32, 0xc23a6033, 0x6aa96730, 0x0d279a31, 0x3e631fc7, 0x59ede2c6,
|
||||
0xf17ee5c5, 0x96f018c4, 0xf371081f, 0x94fff51e, 0x3c6cf21d, 0x5be20f1c,
|
||||
0x68a68aea, 0x0f2877eb, 0xa7bb70e8, 0xc0358de9, 0xc1327b04, 0xa6bc8605,
|
||||
0x0e2f8106, 0x69a17c07, 0x5ae5f9f1, 0x3d6b04f0, 0x95f803f3, 0xf276fef2,
|
||||
0x5efa2245, 0x3974df44, 0x91e7d847, 0xf6692546, 0xc52da0b0, 0xa2a35db1,
|
||||
0x0a305ab2, 0x6dbea7b3, 0x6cb9515e, 0x0b37ac5f, 0xa3a4ab5c, 0xc42a565d,
|
||||
0xf76ed3ab, 0x90e02eaa, 0x387329a9, 0x5ffdd4a8, 0x3a7cc473, 0x5df23972,
|
||||
0xf5613e71, 0x92efc370, 0xa1ab4686, 0xc625bb87, 0x6eb6bc84, 0x09384185,
|
||||
0x083fb768, 0x6fb14a69, 0xc7224d6a, 0xa0acb06b, 0x93e8359d, 0xf466c89c,
|
||||
0x5cf5cf9f, 0x3b7b329e, 0x2a03aaa3, 0x4d8d57a2, 0xe51e50a1, 0x8290ada0,
|
||||
0xb1d42856, 0xd65ad557, 0x7ec9d254, 0x19472f55, 0x1840d9b8, 0x7fce24b9,
|
||||
0xd75d23ba, 0xb0d3debb, 0x83975b4d, 0xe419a64c, 0x4c8aa14f, 0x2b045c4e,
|
||||
0x4e854c95, 0x290bb194, 0x8198b697, 0xe6164b96, 0xd552ce60, 0xb2dc3361,
|
||||
0x1a4f3462, 0x7dc1c963, 0x7cc63f8e, 0x1b48c28f, 0xb3dbc58c, 0xd455388d,
|
||||
0xe711bd7b, 0x809f407a, 0x280c4779, 0x4f82ba78, 0xe30e66cf, 0x84809bce,
|
||||
0x2c139ccd, 0x4b9d61cc, 0x78d9e43a, 0x1f57193b, 0xb7c41e38, 0xd04ae339,
|
||||
0xd14d15d4, 0xb6c3e8d5, 0x1e50efd6, 0x79de12d7, 0x4a9a9721, 0x2d146a20,
|
||||
0x85876d23, 0xe2099022, 0x878880f9, 0xe0067df8, 0x48957afb, 0x2f1b87fa,
|
||||
0x1c5f020c, 0x7bd1ff0d, 0xd342f80e, 0xb4cc050f, 0xb5cbf3e2, 0xd2450ee3,
|
||||
0x7ad609e0, 0x1d58f4e1, 0x2e1c7117, 0x49928c16, 0xe1018b15, 0x868f7614,
|
||||
0xbdf4448a, 0xda7ab98b, 0x72e9be88, 0x15674389, 0x2623c67f, 0x41ad3b7e,
|
||||
0xe93e3c7d, 0x8eb0c17c, 0x8fb73791, 0xe839ca90, 0x40aacd93, 0x27243092,
|
||||
0x1460b564, 0x73ee4865, 0xdb7d4f66, 0xbcf3b267, 0xd972a2bc, 0xbefc5fbd,
|
||||
0x166f58be, 0x71e1a5bf, 0x42a52049, 0x252bdd48, 0x8db8da4b, 0xea36274a,
|
||||
0xeb31d1a7, 0x8cbf2ca6, 0x242c2ba5, 0x43a2d6a4, 0x70e65352, 0x1768ae53,
|
||||
0xbffba950, 0xd8755451, 0x74f988e6, 0x137775e7, 0xbbe472e4, 0xdc6a8fe5,
|
||||
0xef2e0a13, 0x88a0f712, 0x2033f011, 0x47bd0d10, 0x46bafbfd, 0x213406fc,
|
||||
0x89a701ff, 0xee29fcfe, 0xdd6d7908, 0xbae38409, 0x1270830a, 0x75fe7e0b,
|
||||
0x107f6ed0, 0x77f193d1, 0xdf6294d2, 0xb8ec69d3, 0x8ba8ec25, 0xec261124,
|
||||
0x44b51627, 0x233beb26, 0x223c1dcb, 0x45b2e0ca, 0xed21e7c9, 0x8aaf1ac8,
|
||||
0xb9eb9f3e, 0xde65623f, 0x76f6653c, 0x1178983d};
|
||||
|
||||
const uint32_t kStrideExtensionTable3[256] = {
|
||||
0x00000000, 0xf20c0dfe, 0xe1f46d0d, 0x13f860f3, 0xc604aceb, 0x3408a115,
|
||||
0x27f0c1e6, 0xd5fccc18, 0x89e52f27, 0x7be922d9, 0x6811422a, 0x9a1d4fd4,
|
||||
0x4fe183cc, 0xbded8e32, 0xae15eec1, 0x5c19e33f, 0x162628bf, 0xe42a2541,
|
||||
0xf7d245b2, 0x05de484c, 0xd0228454, 0x222e89aa, 0x31d6e959, 0xc3dae4a7,
|
||||
0x9fc30798, 0x6dcf0a66, 0x7e376a95, 0x8c3b676b, 0x59c7ab73, 0xabcba68d,
|
||||
0xb833c67e, 0x4a3fcb80, 0x2c4c517e, 0xde405c80, 0xcdb83c73, 0x3fb4318d,
|
||||
0xea48fd95, 0x1844f06b, 0x0bbc9098, 0xf9b09d66, 0xa5a97e59, 0x57a573a7,
|
||||
0x445d1354, 0xb6511eaa, 0x63add2b2, 0x91a1df4c, 0x8259bfbf, 0x7055b241,
|
||||
0x3a6a79c1, 0xc866743f, 0xdb9e14cc, 0x29921932, 0xfc6ed52a, 0x0e62d8d4,
|
||||
0x1d9ab827, 0xef96b5d9, 0xb38f56e6, 0x41835b18, 0x527b3beb, 0xa0773615,
|
||||
0x758bfa0d, 0x8787f7f3, 0x947f9700, 0x66739afe, 0x5898a2fc, 0xaa94af02,
|
||||
0xb96ccff1, 0x4b60c20f, 0x9e9c0e17, 0x6c9003e9, 0x7f68631a, 0x8d646ee4,
|
||||
0xd17d8ddb, 0x23718025, 0x3089e0d6, 0xc285ed28, 0x17792130, 0xe5752cce,
|
||||
0xf68d4c3d, 0x048141c3, 0x4ebe8a43, 0xbcb287bd, 0xaf4ae74e, 0x5d46eab0,
|
||||
0x88ba26a8, 0x7ab62b56, 0x694e4ba5, 0x9b42465b, 0xc75ba564, 0x3557a89a,
|
||||
0x26afc869, 0xd4a3c597, 0x015f098f, 0xf3530471, 0xe0ab6482, 0x12a7697c,
|
||||
0x74d4f382, 0x86d8fe7c, 0x95209e8f, 0x672c9371, 0xb2d05f69, 0x40dc5297,
|
||||
0x53243264, 0xa1283f9a, 0xfd31dca5, 0x0f3dd15b, 0x1cc5b1a8, 0xeec9bc56,
|
||||
0x3b35704e, 0xc9397db0, 0xdac11d43, 0x28cd10bd, 0x62f2db3d, 0x90fed6c3,
|
||||
0x8306b630, 0x710abbce, 0xa4f677d6, 0x56fa7a28, 0x45021adb, 0xb70e1725,
|
||||
0xeb17f41a, 0x191bf9e4, 0x0ae39917, 0xf8ef94e9, 0x2d1358f1, 0xdf1f550f,
|
||||
0xcce735fc, 0x3eeb3802, 0xb13145f8, 0x433d4806, 0x50c528f5, 0xa2c9250b,
|
||||
0x7735e913, 0x8539e4ed, 0x96c1841e, 0x64cd89e0, 0x38d46adf, 0xcad86721,
|
||||
0xd92007d2, 0x2b2c0a2c, 0xfed0c634, 0x0cdccbca, 0x1f24ab39, 0xed28a6c7,
|
||||
0xa7176d47, 0x551b60b9, 0x46e3004a, 0xb4ef0db4, 0x6113c1ac, 0x931fcc52,
|
||||
0x80e7aca1, 0x72eba15f, 0x2ef24260, 0xdcfe4f9e, 0xcf062f6d, 0x3d0a2293,
|
||||
0xe8f6ee8b, 0x1afae375, 0x09028386, 0xfb0e8e78, 0x9d7d1486, 0x6f711978,
|
||||
0x7c89798b, 0x8e857475, 0x5b79b86d, 0xa975b593, 0xba8dd560, 0x4881d89e,
|
||||
0x14983ba1, 0xe694365f, 0xf56c56ac, 0x07605b52, 0xd29c974a, 0x20909ab4,
|
||||
0x3368fa47, 0xc164f7b9, 0x8b5b3c39, 0x795731c7, 0x6aaf5134, 0x98a35cca,
|
||||
0x4d5f90d2, 0xbf539d2c, 0xacabfddf, 0x5ea7f021, 0x02be131e, 0xf0b21ee0,
|
||||
0xe34a7e13, 0x114673ed, 0xc4babff5, 0x36b6b20b, 0x254ed2f8, 0xd742df06,
|
||||
0xe9a9e704, 0x1ba5eafa, 0x085d8a09, 0xfa5187f7, 0x2fad4bef, 0xdda14611,
|
||||
0xce5926e2, 0x3c552b1c, 0x604cc823, 0x9240c5dd, 0x81b8a52e, 0x73b4a8d0,
|
||||
0xa64864c8, 0x54446936, 0x47bc09c5, 0xb5b0043b, 0xff8fcfbb, 0x0d83c245,
|
||||
0x1e7ba2b6, 0xec77af48, 0x398b6350, 0xcb876eae, 0xd87f0e5d, 0x2a7303a3,
|
||||
0x766ae09c, 0x8466ed62, 0x979e8d91, 0x6592806f, 0xb06e4c77, 0x42624189,
|
||||
0x519a217a, 0xa3962c84, 0xc5e5b67a, 0x37e9bb84, 0x2411db77, 0xd61dd689,
|
||||
0x03e11a91, 0xf1ed176f, 0xe215779c, 0x10197a62, 0x4c00995d, 0xbe0c94a3,
|
||||
0xadf4f450, 0x5ff8f9ae, 0x8a0435b6, 0x78083848, 0x6bf058bb, 0x99fc5545,
|
||||
0xd3c39ec5, 0x21cf933b, 0x3237f3c8, 0xc03bfe36, 0x15c7322e, 0xe7cb3fd0,
|
||||
0xf4335f23, 0x063f52dd, 0x5a26b1e2, 0xa82abc1c, 0xbbd2dcef, 0x49ded111,
|
||||
0x9c221d09, 0x6e2e10f7, 0x7dd67004, 0x8fda7dfa};
|
||||
|
||||
// CRCs are pre- and post- conditioned by xoring with all ones.
|
||||
static constexpr const uint32_t kCRC32Xor = static_cast<uint32_t>(0xffffffffU);
|
||||
|
||||
// Reads a little-endian 32-bit integer from a 32-bit-aligned buffer.
|
||||
inline uint32_t ReadUint32LE(const uint8_t* buffer) {
|
||||
return DecodeFixed32(reinterpret_cast<const char*>(buffer));
|
||||
}
|
||||
|
||||
// Returns the smallest address >= the given address that is aligned to N bytes.
|
||||
//
|
||||
// N must be a power of two.
|
||||
template <int N>
|
||||
constexpr inline const uint8_t* RoundUp(const uint8_t* pointer) {
|
||||
return reinterpret_cast<uint8_t*>(
|
||||
(reinterpret_cast<uintptr_t>(pointer) + (N - 1)) &
|
||||
~static_cast<uintptr_t>(N - 1));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Determine if the CPU running this program can accelerate the CRC32C
|
||||
// calculation.
|
||||
static bool CanAccelerateCRC32C() {
|
||||
// port::AcceleretedCRC32C returns zero when unable to accelerate.
|
||||
static const char kTestCRCBuffer[] = "TestCRCBuffer";
|
||||
static const char kBufSize = sizeof(kTestCRCBuffer) - 1;
|
||||
static const uint32_t kTestCRCValue = 0xdcbc59fa;
|
||||
|
||||
return port::AcceleratedCRC32C(0, kTestCRCBuffer, kBufSize) == kTestCRCValue;
|
||||
}
|
||||
|
||||
uint32_t Extend(uint32_t crc, const char* data, size_t n) {
|
||||
static bool accelerate = CanAccelerateCRC32C();
|
||||
if (accelerate) {
|
||||
return port::AcceleratedCRC32C(crc, data, n);
|
||||
}
|
||||
|
||||
const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
|
||||
const uint8_t* e = p + n;
|
||||
uint32_t l = crc ^ kCRC32Xor;
|
||||
|
||||
// Process one byte at a time.
|
||||
#define STEP1 \
|
||||
do { \
|
||||
int c = (l & 0xff) ^ *p++; \
|
||||
l = kByteExtensionTable[c] ^ (l >> 8); \
|
||||
} while (0)
|
||||
|
||||
// Process one of the 4 strides of 4-byte data.
|
||||
#define STEP4(s) \
|
||||
do { \
|
||||
crc##s = ReadUint32LE(p + s * 4) ^ kStrideExtensionTable3[crc##s & 0xff] ^ \
|
||||
kStrideExtensionTable2[(crc##s >> 8) & 0xff] ^ \
|
||||
kStrideExtensionTable1[(crc##s >> 16) & 0xff] ^ \
|
||||
kStrideExtensionTable0[crc##s >> 24]; \
|
||||
} while (0)
|
||||
|
||||
// Process a 16-byte swath of 4 strides, each of which has 4 bytes of data.
|
||||
#define STEP16 \
|
||||
do { \
|
||||
STEP4(0); \
|
||||
STEP4(1); \
|
||||
STEP4(2); \
|
||||
STEP4(3); \
|
||||
p += 16; \
|
||||
} while (0)
|
||||
|
||||
// Process 4 bytes that were already loaded into a word.
|
||||
#define STEP4W(w) \
|
||||
do { \
|
||||
w ^= l; \
|
||||
for (size_t i = 0; i < 4; ++i) { \
|
||||
w = (w >> 8) ^ kByteExtensionTable[w & 0xff]; \
|
||||
} \
|
||||
l = w; \
|
||||
} while (0)
|
||||
|
||||
// Point x at first 4-byte aligned byte in the buffer. This might be past the
|
||||
// end of the buffer.
|
||||
const uint8_t* x = RoundUp<4>(p);
|
||||
if (x <= e) {
|
||||
// Process bytes p is 4-byte aligned.
|
||||
while (p != x) {
|
||||
STEP1;
|
||||
}
|
||||
}
|
||||
|
||||
if ((e - p) >= 16) {
|
||||
// Load a 16-byte swath into the stride partial results.
|
||||
uint32_t crc0 = ReadUint32LE(p + 0 * 4) ^ l;
|
||||
uint32_t crc1 = ReadUint32LE(p + 1 * 4);
|
||||
uint32_t crc2 = ReadUint32LE(p + 2 * 4);
|
||||
uint32_t crc3 = ReadUint32LE(p + 3 * 4);
|
||||
p += 16;
|
||||
|
||||
// It is possible to get better speeds (at least on x86) by interleaving
|
||||
// prefetching 256 bytes ahead with processing 64 bytes at a time. See the
|
||||
// portable implementation in https://github.com/google/crc32c/.
|
||||
|
||||
// Process one 16-byte swath at a time.
|
||||
while ((e - p) >= 16) {
|
||||
STEP16;
|
||||
}
|
||||
|
||||
// Advance one word at a time as far as possible.
|
||||
while ((e - p) >= 4) {
|
||||
STEP4(0);
|
||||
uint32_t tmp = crc0;
|
||||
crc0 = crc1;
|
||||
crc1 = crc2;
|
||||
crc2 = crc3;
|
||||
crc3 = tmp;
|
||||
p += 4;
|
||||
}
|
||||
|
||||
// Combine the 4 partial stride results.
|
||||
l = 0;
|
||||
STEP4W(crc0);
|
||||
STEP4W(crc1);
|
||||
STEP4W(crc2);
|
||||
STEP4W(crc3);
|
||||
}
|
||||
|
||||
// Process the last few bytes.
|
||||
while (p != e) {
|
||||
STEP1;
|
||||
}
|
||||
#undef STEP4W
|
||||
#undef STEP16
|
||||
#undef STEP4
|
||||
#undef STEP1
|
||||
return l ^ kCRC32Xor;
|
||||
}
|
||||
|
||||
} // namespace crc32c
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_CRC32C_H_
|
||||
#define STORAGE_LEVELDB_UTIL_CRC32C_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace leveldb {
|
||||
namespace crc32c {
|
||||
|
||||
// Return the crc32c of concat(A, data[0,n-1]) where init_crc is the
|
||||
// crc32c of some string A. Extend() is often used to maintain the
|
||||
// crc32c of a stream of data.
|
||||
uint32_t Extend(uint32_t init_crc, const char* data, size_t n);
|
||||
|
||||
// Return the crc32c of data[0,n-1]
|
||||
inline uint32_t Value(const char* data, size_t n) { return Extend(0, data, n); }
|
||||
|
||||
static const uint32_t kMaskDelta = 0xa282ead8ul;
|
||||
|
||||
// Return a masked representation of crc.
|
||||
//
|
||||
// Motivation: it is problematic to compute the CRC of a string that
|
||||
// contains embedded CRCs. Therefore we recommend that CRCs stored
|
||||
// somewhere (e.g., in files) should be masked before being stored.
|
||||
inline uint32_t Mask(uint32_t crc) {
|
||||
// Rotate right by 15 bits and add a constant.
|
||||
return ((crc >> 15) | (crc << 17)) + kMaskDelta;
|
||||
}
|
||||
|
||||
// Return the crc whose masked representation is masked_crc.
|
||||
inline uint32_t Unmask(uint32_t masked_crc) {
|
||||
uint32_t rot = masked_crc - kMaskDelta;
|
||||
return ((rot >> 17) | (rot << 15));
|
||||
}
|
||||
|
||||
} // namespace crc32c
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_CRC32C_H_
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/crc32c.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace leveldb {
|
||||
namespace crc32c {
|
||||
|
||||
TEST(CRC, StandardResults) {
|
||||
// From rfc3720 section B.4.
|
||||
char buf[32];
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
ASSERT_EQ(0x8a9136aa, Value(buf, sizeof(buf)));
|
||||
|
||||
memset(buf, 0xff, sizeof(buf));
|
||||
ASSERT_EQ(0x62a8ab43, Value(buf, sizeof(buf)));
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
buf[i] = i;
|
||||
}
|
||||
ASSERT_EQ(0x46dd794e, Value(buf, sizeof(buf)));
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
buf[i] = 31 - i;
|
||||
}
|
||||
ASSERT_EQ(0x113fdb5c, Value(buf, sizeof(buf)));
|
||||
|
||||
uint8_t data[48] = {
|
||||
0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
ASSERT_EQ(0xd9963a56, Value(reinterpret_cast<char*>(data), sizeof(data)));
|
||||
}
|
||||
|
||||
TEST(CRC, Values) { ASSERT_NE(Value("a", 1), Value("foo", 3)); }
|
||||
|
||||
TEST(CRC, Extend) {
|
||||
ASSERT_EQ(Value("hello world", 11), Extend(Value("hello ", 6), "world", 5));
|
||||
}
|
||||
|
||||
TEST(CRC, Mask) {
|
||||
uint32_t crc = Value("foo", 3);
|
||||
ASSERT_NE(crc, Mask(crc));
|
||||
ASSERT_NE(crc, Mask(Mask(crc)));
|
||||
ASSERT_EQ(crc, Unmask(Mask(crc)));
|
||||
ASSERT_EQ(crc, Unmask(Unmask(Mask(Mask(crc)))));
|
||||
}
|
||||
|
||||
} // namespace crc32c
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/env.h"
|
||||
|
||||
#include <cstdarg>
|
||||
|
||||
// This workaround can be removed when leveldb::Env::DeleteFile is removed.
|
||||
// See env.h for justification.
|
||||
#if defined(_WIN32) && defined(LEVELDB_DELETEFILE_UNDEFINED)
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
Env::Env() = default;
|
||||
|
||||
Env::~Env() = default;
|
||||
|
||||
Status Env::NewAppendableFile(const std::string& fname, WritableFile** result) {
|
||||
return Status::NotSupported("NewAppendableFile", fname);
|
||||
}
|
||||
|
||||
Status Env::RemoveDir(const std::string& dirname) { return DeleteDir(dirname); }
|
||||
Status Env::DeleteDir(const std::string& dirname) { return RemoveDir(dirname); }
|
||||
|
||||
Status Env::RemoveFile(const std::string& fname) { return DeleteFile(fname); }
|
||||
Status Env::DeleteFile(const std::string& fname) { return RemoveFile(fname); }
|
||||
|
||||
SequentialFile::~SequentialFile() = default;
|
||||
|
||||
RandomAccessFile::~RandomAccessFile() = default;
|
||||
|
||||
WritableFile::~WritableFile() = default;
|
||||
|
||||
Logger::~Logger() = default;
|
||||
|
||||
FileLock::~FileLock() = default;
|
||||
|
||||
void Log(Logger* info_log, const char* format, ...) {
|
||||
if (info_log != nullptr) {
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
info_log->Logv(format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
static Status DoWriteStringToFile(Env* env, const Slice& data,
|
||||
const std::string& fname, bool should_sync) {
|
||||
WritableFile* file;
|
||||
Status s = env->NewWritableFile(fname, &file);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = file->Append(data);
|
||||
if (s.ok() && should_sync) {
|
||||
s = file->Sync();
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = file->Close();
|
||||
}
|
||||
delete file; // Will auto-close if we did not close above
|
||||
if (!s.ok()) {
|
||||
env->RemoveFile(fname);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status WriteStringToFile(Env* env, const Slice& data,
|
||||
const std::string& fname) {
|
||||
return DoWriteStringToFile(env, data, fname, false);
|
||||
}
|
||||
|
||||
Status WriteStringToFileSync(Env* env, const Slice& data,
|
||||
const std::string& fname) {
|
||||
return DoWriteStringToFile(env, data, fname, true);
|
||||
}
|
||||
|
||||
Status ReadFileToString(Env* env, const std::string& fname, std::string* data) {
|
||||
data->clear();
|
||||
SequentialFile* file;
|
||||
Status s = env->NewSequentialFile(fname, &file);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
static const int kBufferSize = 8192;
|
||||
char* space = new char[kBufferSize];
|
||||
while (true) {
|
||||
Slice fragment;
|
||||
s = file->Read(kBufferSize, &fragment, space);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
data->append(fragment.data(), fragment.size());
|
||||
if (fragment.empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete[] space;
|
||||
delete file;
|
||||
return s;
|
||||
}
|
||||
|
||||
EnvWrapper::~EnvWrapper() {}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,893 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include "leveldb/status.h"
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
#include "util/env_posix_test_helper.h"
|
||||
#include "util/posix_logger.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
|
||||
// Set by EnvPosixTestHelper::SetReadOnlyMMapLimit() and MaxOpenFiles().
|
||||
int g_open_read_only_file_limit = -1;
|
||||
|
||||
// Up to 1000 mmap regions for 64-bit binaries; none for 32-bit.
|
||||
constexpr const int kDefaultMmapLimit = (sizeof(void*) >= 8) ? 1000 : 0;
|
||||
|
||||
// Can be set using EnvPosixTestHelper::SetReadOnlyMMapLimit().
|
||||
int g_mmap_limit = kDefaultMmapLimit;
|
||||
|
||||
// Common flags defined for all posix open operations
|
||||
#if defined(HAVE_O_CLOEXEC)
|
||||
constexpr const int kOpenBaseFlags = O_CLOEXEC;
|
||||
#else
|
||||
constexpr const int kOpenBaseFlags = 0;
|
||||
#endif // defined(HAVE_O_CLOEXEC)
|
||||
|
||||
constexpr const size_t kWritableFileBufferSize = 65536;
|
||||
|
||||
Status PosixError(const std::string& context, int error_number) {
|
||||
if (error_number == ENOENT) {
|
||||
return Status::NotFound(context, std::strerror(error_number));
|
||||
} else {
|
||||
return Status::IOError(context, std::strerror(error_number));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper class to limit resource usage to avoid exhaustion.
|
||||
// Currently used to limit read-only file descriptors and mmap file usage
|
||||
// so that we do not run out of file descriptors or virtual memory, or run into
|
||||
// kernel performance problems for very large databases.
|
||||
class Limiter {
|
||||
public:
|
||||
// Limit maximum number of resources to |max_acquires|.
|
||||
Limiter(int max_acquires) : acquires_allowed_(max_acquires) {}
|
||||
|
||||
Limiter(const Limiter&) = delete;
|
||||
Limiter operator=(const Limiter&) = delete;
|
||||
|
||||
// If another resource is available, acquire it and return true.
|
||||
// Else return false.
|
||||
bool Acquire() {
|
||||
int old_acquires_allowed =
|
||||
acquires_allowed_.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
if (old_acquires_allowed > 0) return true;
|
||||
|
||||
acquires_allowed_.fetch_add(1, std::memory_order_relaxed);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release a resource acquired by a previous call to Acquire() that returned
|
||||
// true.
|
||||
void Release() { acquires_allowed_.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
// The number of available resources.
|
||||
//
|
||||
// This is a counter and is not tied to the invariants of any other class, so
|
||||
// it can be operated on safely using std::memory_order_relaxed.
|
||||
std::atomic<int> acquires_allowed_;
|
||||
};
|
||||
|
||||
// Implements sequential read access in a file using read().
|
||||
//
|
||||
// Instances of this class are thread-friendly but not thread-safe, as required
|
||||
// by the SequentialFile API.
|
||||
class PosixSequentialFile final : public SequentialFile {
|
||||
public:
|
||||
PosixSequentialFile(std::string filename, int fd)
|
||||
: fd_(fd), filename_(filename) {}
|
||||
~PosixSequentialFile() override { close(fd_); }
|
||||
|
||||
Status Read(size_t n, Slice* result, char* scratch) override {
|
||||
Status status;
|
||||
while (true) {
|
||||
::ssize_t read_size = ::read(fd_, scratch, n);
|
||||
if (read_size < 0) { // Read error.
|
||||
if (errno == EINTR) {
|
||||
continue; // Retry
|
||||
}
|
||||
status = PosixError(filename_, errno);
|
||||
break;
|
||||
}
|
||||
*result = Slice(scratch, read_size);
|
||||
break;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status Skip(uint64_t n) override {
|
||||
if (::lseek(fd_, n, SEEK_CUR) == static_cast<off_t>(-1)) {
|
||||
return PosixError(filename_, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
const int fd_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
// Implements random read access in a file using pread().
|
||||
//
|
||||
// Instances of this class are thread-safe, as required by the RandomAccessFile
|
||||
// API. Instances are immutable and Read() only calls thread-safe library
|
||||
// functions.
|
||||
class PosixRandomAccessFile final : public RandomAccessFile {
|
||||
public:
|
||||
// The new instance takes ownership of |fd|. |fd_limiter| must outlive this
|
||||
// instance, and will be used to determine if .
|
||||
PosixRandomAccessFile(std::string filename, int fd, Limiter* fd_limiter)
|
||||
: has_permanent_fd_(fd_limiter->Acquire()),
|
||||
fd_(has_permanent_fd_ ? fd : -1),
|
||||
fd_limiter_(fd_limiter),
|
||||
filename_(std::move(filename)) {
|
||||
if (!has_permanent_fd_) {
|
||||
assert(fd_ == -1);
|
||||
::close(fd); // The file will be opened on every read.
|
||||
}
|
||||
}
|
||||
|
||||
~PosixRandomAccessFile() override {
|
||||
if (has_permanent_fd_) {
|
||||
assert(fd_ != -1);
|
||||
::close(fd_);
|
||||
fd_limiter_->Release();
|
||||
}
|
||||
}
|
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
int fd = fd_;
|
||||
if (!has_permanent_fd_) {
|
||||
fd = ::open(filename_.c_str(), O_RDONLY | kOpenBaseFlags);
|
||||
if (fd < 0) {
|
||||
return PosixError(filename_, errno);
|
||||
}
|
||||
}
|
||||
|
||||
assert(fd != -1);
|
||||
|
||||
Status status;
|
||||
ssize_t read_size = ::pread(fd, scratch, n, static_cast<off_t>(offset));
|
||||
*result = Slice(scratch, (read_size < 0) ? 0 : read_size);
|
||||
if (read_size < 0) {
|
||||
// An error: return a non-ok status.
|
||||
status = PosixError(filename_, errno);
|
||||
}
|
||||
if (!has_permanent_fd_) {
|
||||
// Close the temporary file descriptor opened earlier.
|
||||
assert(fd != fd_);
|
||||
::close(fd);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private:
|
||||
const bool has_permanent_fd_; // If false, the file is opened on every read.
|
||||
const int fd_; // -1 if has_permanent_fd_ is false.
|
||||
Limiter* const fd_limiter_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
// Implements random read access in a file using mmap().
|
||||
//
|
||||
// Instances of this class are thread-safe, as required by the RandomAccessFile
|
||||
// API. Instances are immutable and Read() only calls thread-safe library
|
||||
// functions.
|
||||
class PosixMmapReadableFile final : public RandomAccessFile {
|
||||
public:
|
||||
// mmap_base[0, length-1] points to the memory-mapped contents of the file. It
|
||||
// must be the result of a successful call to mmap(). This instances takes
|
||||
// over the ownership of the region.
|
||||
//
|
||||
// |mmap_limiter| must outlive this instance. The caller must have already
|
||||
// aquired the right to use one mmap region, which will be released when this
|
||||
// instance is destroyed.
|
||||
PosixMmapReadableFile(std::string filename, char* mmap_base, size_t length,
|
||||
Limiter* mmap_limiter)
|
||||
: mmap_base_(mmap_base),
|
||||
length_(length),
|
||||
mmap_limiter_(mmap_limiter),
|
||||
filename_(std::move(filename)) {}
|
||||
|
||||
~PosixMmapReadableFile() override {
|
||||
::munmap(static_cast<void*>(mmap_base_), length_);
|
||||
mmap_limiter_->Release();
|
||||
}
|
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
if (offset + n > length_) {
|
||||
*result = Slice();
|
||||
return PosixError(filename_, EINVAL);
|
||||
}
|
||||
|
||||
*result = Slice(mmap_base_ + offset, n);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
char* const mmap_base_;
|
||||
const size_t length_;
|
||||
Limiter* const mmap_limiter_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
class PosixWritableFile final : public WritableFile {
|
||||
public:
|
||||
PosixWritableFile(std::string filename, int fd)
|
||||
: pos_(0),
|
||||
fd_(fd),
|
||||
is_manifest_(IsManifest(filename)),
|
||||
filename_(std::move(filename)),
|
||||
dirname_(Dirname(filename_)) {}
|
||||
|
||||
~PosixWritableFile() override {
|
||||
if (fd_ >= 0) {
|
||||
// Ignoring any potential errors
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
Status Append(const Slice& data) override {
|
||||
size_t write_size = data.size();
|
||||
const char* write_data = data.data();
|
||||
|
||||
// Fit as much as possible into buffer.
|
||||
size_t copy_size = std::min(write_size, kWritableFileBufferSize - pos_);
|
||||
std::memcpy(buf_ + pos_, write_data, copy_size);
|
||||
write_data += copy_size;
|
||||
write_size -= copy_size;
|
||||
pos_ += copy_size;
|
||||
if (write_size == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Can't fit in buffer, so need to do at least one write.
|
||||
Status status = FlushBuffer();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Small writes go to buffer, large writes are written directly.
|
||||
if (write_size < kWritableFileBufferSize) {
|
||||
std::memcpy(buf_, write_data, write_size);
|
||||
pos_ = write_size;
|
||||
return Status::OK();
|
||||
}
|
||||
return WriteUnbuffered(write_data, write_size);
|
||||
}
|
||||
|
||||
Status Close() override {
|
||||
Status status = FlushBuffer();
|
||||
const int close_result = ::close(fd_);
|
||||
if (close_result < 0 && status.ok()) {
|
||||
status = PosixError(filename_, errno);
|
||||
}
|
||||
fd_ = -1;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status Flush() override { return FlushBuffer(); }
|
||||
|
||||
Status Sync() override {
|
||||
// Ensure new files referred to by the manifest are in the filesystem.
|
||||
//
|
||||
// This needs to happen before the manifest file is flushed to disk, to
|
||||
// avoid crashing in a state where the manifest refers to files that are not
|
||||
// yet on disk.
|
||||
Status status = SyncDirIfManifest();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = FlushBuffer();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return SyncFd(fd_, filename_);
|
||||
}
|
||||
|
||||
private:
|
||||
Status FlushBuffer() {
|
||||
Status status = WriteUnbuffered(buf_, pos_);
|
||||
pos_ = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status WriteUnbuffered(const char* data, size_t size) {
|
||||
while (size > 0) {
|
||||
ssize_t write_result = ::write(fd_, data, size);
|
||||
if (write_result < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue; // Retry
|
||||
}
|
||||
return PosixError(filename_, errno);
|
||||
}
|
||||
data += write_result;
|
||||
size -= write_result;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SyncDirIfManifest() {
|
||||
Status status;
|
||||
if (!is_manifest_) {
|
||||
return status;
|
||||
}
|
||||
|
||||
int fd = ::open(dirname_.c_str(), O_RDONLY | kOpenBaseFlags);
|
||||
if (fd < 0) {
|
||||
status = PosixError(dirname_, errno);
|
||||
} else {
|
||||
status = SyncFd(fd, dirname_);
|
||||
::close(fd);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Ensures that all the caches associated with the given file descriptor's
|
||||
// data are flushed all the way to durable media, and can withstand power
|
||||
// failures.
|
||||
//
|
||||
// The path argument is only used to populate the description string in the
|
||||
// returned Status if an error occurs.
|
||||
static Status SyncFd(int fd, const std::string& fd_path) {
|
||||
#if HAVE_FULLFSYNC
|
||||
// On macOS and iOS, fsync() doesn't guarantee durability past power
|
||||
// failures. fcntl(F_FULLFSYNC) is required for that purpose. Some
|
||||
// filesystems don't support fcntl(F_FULLFSYNC), and require a fallback to
|
||||
// fsync().
|
||||
if (::fcntl(fd, F_FULLFSYNC) == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
#endif // HAVE_FULLFSYNC
|
||||
|
||||
#if HAVE_FDATASYNC
|
||||
bool sync_success = ::fdatasync(fd) == 0;
|
||||
#else
|
||||
bool sync_success = ::fsync(fd) == 0;
|
||||
#endif // HAVE_FDATASYNC
|
||||
|
||||
if (sync_success) {
|
||||
return Status::OK();
|
||||
}
|
||||
return PosixError(fd_path, errno);
|
||||
}
|
||||
|
||||
// Returns the directory name in a path pointing to a file.
|
||||
//
|
||||
// Returns "." if the path does not contain any directory separator.
|
||||
static std::string Dirname(const std::string& filename) {
|
||||
std::string::size_type separator_pos = filename.rfind('/');
|
||||
if (separator_pos == std::string::npos) {
|
||||
return std::string(".");
|
||||
}
|
||||
// The filename component should not contain a path separator. If it does,
|
||||
// the splitting was done incorrectly.
|
||||
assert(filename.find('/', separator_pos + 1) == std::string::npos);
|
||||
|
||||
return filename.substr(0, separator_pos);
|
||||
}
|
||||
|
||||
// Extracts the file name from a path pointing to a file.
|
||||
//
|
||||
// The returned Slice points to |filename|'s data buffer, so it is only valid
|
||||
// while |filename| is alive and unchanged.
|
||||
static Slice Basename(const std::string& filename) {
|
||||
std::string::size_type separator_pos = filename.rfind('/');
|
||||
if (separator_pos == std::string::npos) {
|
||||
return Slice(filename);
|
||||
}
|
||||
// The filename component should not contain a path separator. If it does,
|
||||
// the splitting was done incorrectly.
|
||||
assert(filename.find('/', separator_pos + 1) == std::string::npos);
|
||||
|
||||
return Slice(filename.data() + separator_pos + 1,
|
||||
filename.length() - separator_pos - 1);
|
||||
}
|
||||
|
||||
// True if the given file is a manifest file.
|
||||
static bool IsManifest(const std::string& filename) {
|
||||
return Basename(filename).starts_with("MANIFEST");
|
||||
}
|
||||
|
||||
// buf_[0, pos_ - 1] contains data to be written to fd_.
|
||||
char buf_[kWritableFileBufferSize];
|
||||
size_t pos_;
|
||||
int fd_;
|
||||
|
||||
const bool is_manifest_; // True if the file's name starts with MANIFEST.
|
||||
const std::string filename_;
|
||||
const std::string dirname_; // The directory of filename_.
|
||||
};
|
||||
|
||||
int LockOrUnlock(int fd, bool lock) {
|
||||
errno = 0;
|
||||
struct ::flock file_lock_info;
|
||||
std::memset(&file_lock_info, 0, sizeof(file_lock_info));
|
||||
file_lock_info.l_type = (lock ? F_WRLCK : F_UNLCK);
|
||||
file_lock_info.l_whence = SEEK_SET;
|
||||
file_lock_info.l_start = 0;
|
||||
file_lock_info.l_len = 0; // Lock/unlock entire file.
|
||||
return ::fcntl(fd, F_SETLK, &file_lock_info);
|
||||
}
|
||||
|
||||
// Instances are thread-safe because they are immutable.
|
||||
class PosixFileLock : public FileLock {
|
||||
public:
|
||||
PosixFileLock(int fd, std::string filename)
|
||||
: fd_(fd), filename_(std::move(filename)) {}
|
||||
|
||||
int fd() const { return fd_; }
|
||||
const std::string& filename() const { return filename_; }
|
||||
|
||||
private:
|
||||
const int fd_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
// Tracks the files locked by PosixEnv::LockFile().
|
||||
//
|
||||
// We maintain a separate set instead of relying on fcntl(F_SETLK) because
|
||||
// fcntl(F_SETLK) does not provide any protection against multiple uses from the
|
||||
// same process.
|
||||
//
|
||||
// Instances are thread-safe because all member data is guarded by a mutex.
|
||||
class PosixLockTable {
|
||||
public:
|
||||
bool Insert(const std::string& fname) LOCKS_EXCLUDED(mu_) {
|
||||
mu_.Lock();
|
||||
bool succeeded = locked_files_.insert(fname).second;
|
||||
mu_.Unlock();
|
||||
return succeeded;
|
||||
}
|
||||
void Remove(const std::string& fname) LOCKS_EXCLUDED(mu_) {
|
||||
mu_.Lock();
|
||||
locked_files_.erase(fname);
|
||||
mu_.Unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
std::set<std::string> locked_files_ GUARDED_BY(mu_);
|
||||
};
|
||||
|
||||
class PosixEnv : public Env {
|
||||
public:
|
||||
PosixEnv();
|
||||
~PosixEnv() override {
|
||||
static const char msg[] =
|
||||
"PosixEnv singleton destroyed. Unsupported behavior!\n";
|
||||
std::fwrite(msg, 1, sizeof(msg), stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Status NewSequentialFile(const std::string& filename,
|
||||
SequentialFile** result) override {
|
||||
int fd = ::open(filename.c_str(), O_RDONLY | kOpenBaseFlags);
|
||||
if (fd < 0) {
|
||||
*result = nullptr;
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
|
||||
*result = new PosixSequentialFile(filename, fd);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewRandomAccessFile(const std::string& filename,
|
||||
RandomAccessFile** result) override {
|
||||
*result = nullptr;
|
||||
int fd = ::open(filename.c_str(), O_RDONLY | kOpenBaseFlags);
|
||||
if (fd < 0) {
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
|
||||
if (!mmap_limiter_.Acquire()) {
|
||||
*result = new PosixRandomAccessFile(filename, fd, &fd_limiter_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t file_size;
|
||||
Status status = GetFileSize(filename, &file_size);
|
||||
if (status.ok()) {
|
||||
void* mmap_base =
|
||||
::mmap(/*addr=*/nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (mmap_base != MAP_FAILED) {
|
||||
*result = new PosixMmapReadableFile(filename,
|
||||
reinterpret_cast<char*>(mmap_base),
|
||||
file_size, &mmap_limiter_);
|
||||
} else {
|
||||
status = PosixError(filename, errno);
|
||||
}
|
||||
}
|
||||
::close(fd);
|
||||
if (!status.ok()) {
|
||||
mmap_limiter_.Release();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& filename,
|
||||
WritableFile** result) override {
|
||||
int fd = ::open(filename.c_str(),
|
||||
O_TRUNC | O_WRONLY | O_CREAT | kOpenBaseFlags, 0644);
|
||||
if (fd < 0) {
|
||||
*result = nullptr;
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
|
||||
*result = new PosixWritableFile(filename, fd);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewAppendableFile(const std::string& filename,
|
||||
WritableFile** result) override {
|
||||
int fd = ::open(filename.c_str(),
|
||||
O_APPEND | O_WRONLY | O_CREAT | kOpenBaseFlags, 0644);
|
||||
if (fd < 0) {
|
||||
*result = nullptr;
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
|
||||
*result = new PosixWritableFile(filename, fd);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool FileExists(const std::string& filename) override {
|
||||
return ::access(filename.c_str(), F_OK) == 0;
|
||||
}
|
||||
|
||||
Status GetChildren(const std::string& directory_path,
|
||||
std::vector<std::string>* result) override {
|
||||
result->clear();
|
||||
::DIR* dir = ::opendir(directory_path.c_str());
|
||||
if (dir == nullptr) {
|
||||
return PosixError(directory_path, errno);
|
||||
}
|
||||
struct ::dirent* entry;
|
||||
while ((entry = ::readdir(dir)) != nullptr) {
|
||||
result->emplace_back(entry->d_name);
|
||||
}
|
||||
::closedir(dir);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RemoveFile(const std::string& filename) override {
|
||||
if (::unlink(filename.c_str()) != 0) {
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CreateDir(const std::string& dirname) override {
|
||||
if (::mkdir(dirname.c_str(), 0755) != 0) {
|
||||
return PosixError(dirname, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RemoveDir(const std::string& dirname) override {
|
||||
if (::rmdir(dirname.c_str()) != 0) {
|
||||
return PosixError(dirname, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetFileSize(const std::string& filename, uint64_t* size) override {
|
||||
struct ::stat file_stat;
|
||||
if (::stat(filename.c_str(), &file_stat) != 0) {
|
||||
*size = 0;
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
*size = file_stat.st_size;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RenameFile(const std::string& from, const std::string& to) override {
|
||||
if (std::rename(from.c_str(), to.c_str()) != 0) {
|
||||
return PosixError(from, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status LockFile(const std::string& filename, FileLock** lock) override {
|
||||
*lock = nullptr;
|
||||
|
||||
int fd = ::open(filename.c_str(), O_RDWR | O_CREAT | kOpenBaseFlags, 0644);
|
||||
if (fd < 0) {
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
|
||||
if (!locks_.Insert(filename)) {
|
||||
::close(fd);
|
||||
return Status::IOError("lock " + filename, "already held by process");
|
||||
}
|
||||
|
||||
if (LockOrUnlock(fd, true) == -1) {
|
||||
int lock_errno = errno;
|
||||
::close(fd);
|
||||
locks_.Remove(filename);
|
||||
return PosixError("lock " + filename, lock_errno);
|
||||
}
|
||||
|
||||
*lock = new PosixFileLock(fd, filename);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status UnlockFile(FileLock* lock) override {
|
||||
PosixFileLock* posix_file_lock = static_cast<PosixFileLock*>(lock);
|
||||
if (LockOrUnlock(posix_file_lock->fd(), false) == -1) {
|
||||
return PosixError("unlock " + posix_file_lock->filename(), errno);
|
||||
}
|
||||
locks_.Remove(posix_file_lock->filename());
|
||||
::close(posix_file_lock->fd());
|
||||
delete posix_file_lock;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void Schedule(void (*background_work_function)(void* background_work_arg),
|
||||
void* background_work_arg) override;
|
||||
|
||||
void StartThread(void (*thread_main)(void* thread_main_arg),
|
||||
void* thread_main_arg) override {
|
||||
std::thread new_thread(thread_main, thread_main_arg);
|
||||
new_thread.detach();
|
||||
}
|
||||
|
||||
Status GetTestDirectory(std::string* result) override {
|
||||
const char* env = std::getenv("TEST_TMPDIR");
|
||||
if (env && env[0] != '\0') {
|
||||
*result = env;
|
||||
} else {
|
||||
char buf[100];
|
||||
std::snprintf(buf, sizeof(buf), "/tmp/leveldbtest-%d",
|
||||
static_cast<int>(::geteuid()));
|
||||
*result = buf;
|
||||
}
|
||||
|
||||
// The CreateDir status is ignored because the directory may already exist.
|
||||
CreateDir(*result);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewLogger(const std::string& filename, Logger** result) override {
|
||||
int fd = ::open(filename.c_str(),
|
||||
O_APPEND | O_WRONLY | O_CREAT | kOpenBaseFlags, 0644);
|
||||
if (fd < 0) {
|
||||
*result = nullptr;
|
||||
return PosixError(filename, errno);
|
||||
}
|
||||
|
||||
std::FILE* fp = ::fdopen(fd, "w");
|
||||
if (fp == nullptr) {
|
||||
::close(fd);
|
||||
*result = nullptr;
|
||||
return PosixError(filename, errno);
|
||||
} else {
|
||||
*result = new PosixLogger(fp);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t NowMicros() override {
|
||||
static constexpr uint64_t kUsecondsPerSecond = 1000000;
|
||||
struct ::timeval tv;
|
||||
::gettimeofday(&tv, nullptr);
|
||||
return static_cast<uint64_t>(tv.tv_sec) * kUsecondsPerSecond + tv.tv_usec;
|
||||
}
|
||||
|
||||
void SleepForMicroseconds(int micros) override {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
||||
}
|
||||
|
||||
private:
|
||||
void BackgroundThreadMain();
|
||||
|
||||
static void BackgroundThreadEntryPoint(PosixEnv* env) {
|
||||
env->BackgroundThreadMain();
|
||||
}
|
||||
|
||||
// Stores the work item data in a Schedule() call.
|
||||
//
|
||||
// Instances are constructed on the thread calling Schedule() and used on the
|
||||
// background thread.
|
||||
//
|
||||
// This structure is thread-safe beacuse it is immutable.
|
||||
struct BackgroundWorkItem {
|
||||
explicit BackgroundWorkItem(void (*function)(void* arg), void* arg)
|
||||
: function(function), arg(arg) {}
|
||||
|
||||
void (*const function)(void*);
|
||||
void* const arg;
|
||||
};
|
||||
|
||||
port::Mutex background_work_mutex_;
|
||||
port::CondVar background_work_cv_ GUARDED_BY(background_work_mutex_);
|
||||
bool started_background_thread_ GUARDED_BY(background_work_mutex_);
|
||||
|
||||
std::queue<BackgroundWorkItem> background_work_queue_
|
||||
GUARDED_BY(background_work_mutex_);
|
||||
|
||||
PosixLockTable locks_; // Thread-safe.
|
||||
Limiter mmap_limiter_; // Thread-safe.
|
||||
Limiter fd_limiter_; // Thread-safe.
|
||||
};
|
||||
|
||||
// Return the maximum number of concurrent mmaps.
|
||||
int MaxMmaps() { return g_mmap_limit; }
|
||||
|
||||
// Return the maximum number of read-only files to keep open.
|
||||
int MaxOpenFiles() {
|
||||
if (g_open_read_only_file_limit >= 0) {
|
||||
return g_open_read_only_file_limit;
|
||||
}
|
||||
struct ::rlimit rlim;
|
||||
if (::getrlimit(RLIMIT_NOFILE, &rlim)) {
|
||||
// getrlimit failed, fallback to hard-coded default.
|
||||
g_open_read_only_file_limit = 50;
|
||||
} else if (rlim.rlim_cur == RLIM_INFINITY) {
|
||||
g_open_read_only_file_limit = std::numeric_limits<int>::max();
|
||||
} else {
|
||||
// Allow use of 20% of available file descriptors for read-only files.
|
||||
g_open_read_only_file_limit = rlim.rlim_cur / 5;
|
||||
}
|
||||
return g_open_read_only_file_limit;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PosixEnv::PosixEnv()
|
||||
: background_work_cv_(&background_work_mutex_),
|
||||
started_background_thread_(false),
|
||||
mmap_limiter_(MaxMmaps()),
|
||||
fd_limiter_(MaxOpenFiles()) {}
|
||||
|
||||
void PosixEnv::Schedule(
|
||||
void (*background_work_function)(void* background_work_arg),
|
||||
void* background_work_arg) {
|
||||
background_work_mutex_.Lock();
|
||||
|
||||
// Start the background thread, if we haven't done so already.
|
||||
if (!started_background_thread_) {
|
||||
started_background_thread_ = true;
|
||||
std::thread background_thread(PosixEnv::BackgroundThreadEntryPoint, this);
|
||||
background_thread.detach();
|
||||
}
|
||||
|
||||
// If the queue is empty, the background thread may be waiting for work.
|
||||
if (background_work_queue_.empty()) {
|
||||
background_work_cv_.Signal();
|
||||
}
|
||||
|
||||
background_work_queue_.emplace(background_work_function, background_work_arg);
|
||||
background_work_mutex_.Unlock();
|
||||
}
|
||||
|
||||
void PosixEnv::BackgroundThreadMain() {
|
||||
while (true) {
|
||||
background_work_mutex_.Lock();
|
||||
|
||||
// Wait until there is work to be done.
|
||||
while (background_work_queue_.empty()) {
|
||||
background_work_cv_.Wait();
|
||||
}
|
||||
|
||||
assert(!background_work_queue_.empty());
|
||||
auto background_work_function = background_work_queue_.front().function;
|
||||
void* background_work_arg = background_work_queue_.front().arg;
|
||||
background_work_queue_.pop();
|
||||
|
||||
background_work_mutex_.Unlock();
|
||||
background_work_function(background_work_arg);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Wraps an Env instance whose destructor is never created.
|
||||
//
|
||||
// Intended usage:
|
||||
// using PlatformSingletonEnv = SingletonEnv<PlatformEnv>;
|
||||
// void ConfigurePosixEnv(int param) {
|
||||
// PlatformSingletonEnv::AssertEnvNotInitialized();
|
||||
// // set global configuration flags.
|
||||
// }
|
||||
// Env* Env::Default() {
|
||||
// static PlatformSingletonEnv default_env;
|
||||
// return default_env.env();
|
||||
// }
|
||||
template <typename EnvType>
|
||||
class SingletonEnv {
|
||||
public:
|
||||
SingletonEnv() {
|
||||
#if !defined(NDEBUG)
|
||||
env_initialized_.store(true, std::memory_order::memory_order_relaxed);
|
||||
#endif // !defined(NDEBUG)
|
||||
static_assert(sizeof(env_storage_) >= sizeof(EnvType),
|
||||
"env_storage_ will not fit the Env");
|
||||
static_assert(alignof(decltype(env_storage_)) >= alignof(EnvType),
|
||||
"env_storage_ does not meet the Env's alignment needs");
|
||||
new (&env_storage_) EnvType();
|
||||
}
|
||||
~SingletonEnv() = default;
|
||||
|
||||
SingletonEnv(const SingletonEnv&) = delete;
|
||||
SingletonEnv& operator=(const SingletonEnv&) = delete;
|
||||
|
||||
Env* env() { return reinterpret_cast<Env*>(&env_storage_); }
|
||||
|
||||
static void AssertEnvNotInitialized() {
|
||||
#if !defined(NDEBUG)
|
||||
assert(!env_initialized_.load(std::memory_order::memory_order_relaxed));
|
||||
#endif // !defined(NDEBUG)
|
||||
}
|
||||
|
||||
private:
|
||||
typename std::aligned_storage<sizeof(EnvType), alignof(EnvType)>::type
|
||||
env_storage_;
|
||||
#if !defined(NDEBUG)
|
||||
static std::atomic<bool> env_initialized_;
|
||||
#endif // !defined(NDEBUG)
|
||||
};
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
template <typename EnvType>
|
||||
std::atomic<bool> SingletonEnv<EnvType>::env_initialized_;
|
||||
#endif // !defined(NDEBUG)
|
||||
|
||||
using PosixDefaultEnv = SingletonEnv<PosixEnv>;
|
||||
|
||||
} // namespace
|
||||
|
||||
void EnvPosixTestHelper::SetReadOnlyFDLimit(int limit) {
|
||||
PosixDefaultEnv::AssertEnvNotInitialized();
|
||||
g_open_read_only_file_limit = limit;
|
||||
}
|
||||
|
||||
void EnvPosixTestHelper::SetReadOnlyMMapLimit(int limit) {
|
||||
PosixDefaultEnv::AssertEnvNotInitialized();
|
||||
g_mmap_limit = limit;
|
||||
}
|
||||
|
||||
Env* Env::Default() {
|
||||
static PosixDefaultEnv env_container;
|
||||
return env_container.env();
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,353 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <sys/resource.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "util/env_posix_test_helper.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
#if HAVE_O_CLOEXEC
|
||||
|
||||
namespace {
|
||||
|
||||
// Exit codes for the helper process spawned by TestCloseOnExec* tests.
|
||||
// Useful for debugging test failures.
|
||||
constexpr int kTextCloseOnExecHelperExecFailedCode = 61;
|
||||
constexpr int kTextCloseOnExecHelperDup2FailedCode = 62;
|
||||
constexpr int kTextCloseOnExecHelperFoundOpenFdCode = 63;
|
||||
|
||||
// Global set by main() and read in TestCloseOnExec.
|
||||
//
|
||||
// The argv[0] value is stored in a std::vector instead of a std::string because
|
||||
// std::string does not return a mutable pointer to its buffer until C++17.
|
||||
//
|
||||
// The vector stores the string pointed to by argv[0], plus the trailing null.
|
||||
std::vector<char>* GetArgvZero() {
|
||||
static std::vector<char> program_name;
|
||||
return &program_name;
|
||||
}
|
||||
|
||||
// Command-line switch used to run this test as the CloseOnExecSwitch helper.
|
||||
static const char kTestCloseOnExecSwitch[] = "--test-close-on-exec-helper";
|
||||
|
||||
// Executed in a separate process by TestCloseOnExec* tests.
|
||||
//
|
||||
// main() delegates to this function when the test executable is launched with
|
||||
// a special command-line switch. TestCloseOnExec* tests fork()+exec() the test
|
||||
// executable and pass the special command-line switch.
|
||||
//
|
||||
|
||||
// main() delegates to this function when the test executable is launched with
|
||||
// a special command-line switch. TestCloseOnExec* tests fork()+exec() the test
|
||||
// executable and pass the special command-line switch.
|
||||
//
|
||||
// When main() delegates to this function, the process probes whether a given
|
||||
// file descriptor is open, and communicates the result via its exit code.
|
||||
int TestCloseOnExecHelperMain(char* pid_arg) {
|
||||
int fd = std::atoi(pid_arg);
|
||||
// When given the same file descriptor twice, dup2() returns -1 if the
|
||||
// file descriptor is closed, or the given file descriptor if it is open.
|
||||
if (::dup2(fd, fd) == fd) {
|
||||
std::fprintf(stderr, "Unexpected open fd %d\n", fd);
|
||||
return kTextCloseOnExecHelperFoundOpenFdCode;
|
||||
}
|
||||
// Double-check that dup2() is saying the file descriptor is closed.
|
||||
if (errno != EBADF) {
|
||||
std::fprintf(stderr, "Unexpected errno after calling dup2 on fd %d: %s\n",
|
||||
fd, std::strerror(errno));
|
||||
return kTextCloseOnExecHelperDup2FailedCode;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// File descriptors are small non-negative integers.
|
||||
//
|
||||
// Returns void so the implementation can use ASSERT_EQ.
|
||||
void GetMaxFileDescriptor(int* result_fd) {
|
||||
// Get the maximum file descriptor number.
|
||||
::rlimit fd_rlimit;
|
||||
ASSERT_EQ(0, ::getrlimit(RLIMIT_NOFILE, &fd_rlimit));
|
||||
*result_fd = fd_rlimit.rlim_cur;
|
||||
}
|
||||
|
||||
// Iterates through all possible FDs and returns the currently open ones.
|
||||
//
|
||||
// Returns void so the implementation can use ASSERT_EQ.
|
||||
void GetOpenFileDescriptors(std::unordered_set<int>* open_fds) {
|
||||
int max_fd = 0;
|
||||
GetMaxFileDescriptor(&max_fd);
|
||||
|
||||
for (int fd = 0; fd < max_fd; ++fd) {
|
||||
if (::dup2(fd, fd) != fd) {
|
||||
// When given the same file descriptor twice, dup2() returns -1 if the
|
||||
// file descriptor is closed, or the given file descriptor if it is open.
|
||||
//
|
||||
// Double-check that dup2() is saying the fd is closed.
|
||||
ASSERT_EQ(EBADF, errno)
|
||||
<< "dup2() should set errno to EBADF on closed file descriptors";
|
||||
continue;
|
||||
}
|
||||
open_fds->insert(fd);
|
||||
}
|
||||
}
|
||||
|
||||
// Finds an FD open since a previous call to GetOpenFileDescriptors().
|
||||
//
|
||||
// |baseline_open_fds| is the result of a previous GetOpenFileDescriptors()
|
||||
// call. Assumes that exactly one FD was opened since that call.
|
||||
//
|
||||
// Returns void so the implementation can use ASSERT_EQ.
|
||||
void GetNewlyOpenedFileDescriptor(
|
||||
const std::unordered_set<int>& baseline_open_fds, int* result_fd) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
for (int fd : baseline_open_fds) {
|
||||
ASSERT_EQ(1, open_fds.count(fd))
|
||||
<< "Previously opened file descriptor was closed during test setup";
|
||||
open_fds.erase(fd);
|
||||
}
|
||||
ASSERT_EQ(1, open_fds.size())
|
||||
<< "Expected exactly one newly opened file descriptor during test setup";
|
||||
*result_fd = *open_fds.begin();
|
||||
}
|
||||
|
||||
// Check that a fork()+exec()-ed child process does not have an extra open FD.
|
||||
void CheckCloseOnExecDoesNotLeakFDs(
|
||||
const std::unordered_set<int>& baseline_open_fds) {
|
||||
// Prepare the argument list for the child process.
|
||||
// execv() wants mutable buffers.
|
||||
char switch_buffer[sizeof(kTestCloseOnExecSwitch)];
|
||||
std::memcpy(switch_buffer, kTestCloseOnExecSwitch,
|
||||
sizeof(kTestCloseOnExecSwitch));
|
||||
|
||||
int probed_fd;
|
||||
GetNewlyOpenedFileDescriptor(baseline_open_fds, &probed_fd);
|
||||
std::string fd_string = std::to_string(probed_fd);
|
||||
std::vector<char> fd_buffer(fd_string.begin(), fd_string.end());
|
||||
fd_buffer.emplace_back('\0');
|
||||
|
||||
// The helper process is launched with the command below.
|
||||
// env_posix_tests --test-close-on-exec-helper 3
|
||||
char* child_argv[] = {GetArgvZero()->data(), switch_buffer, fd_buffer.data(),
|
||||
nullptr};
|
||||
|
||||
constexpr int kForkInChildProcessReturnValue = 0;
|
||||
int child_pid = fork();
|
||||
if (child_pid == kForkInChildProcessReturnValue) {
|
||||
::execv(child_argv[0], child_argv);
|
||||
std::fprintf(stderr, "Error spawning child process: %s\n", strerror(errno));
|
||||
std::exit(kTextCloseOnExecHelperExecFailedCode);
|
||||
}
|
||||
|
||||
int child_status = 0;
|
||||
ASSERT_EQ(child_pid, ::waitpid(child_pid, &child_status, 0));
|
||||
ASSERT_TRUE(WIFEXITED(child_status))
|
||||
<< "The helper process did not exit with an exit code";
|
||||
ASSERT_EQ(0, WEXITSTATUS(child_status))
|
||||
<< "The helper process encountered an error";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // HAVE_O_CLOEXEC
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const int kReadOnlyFileLimit = 4;
|
||||
static const int kMMapLimit = 4;
|
||||
|
||||
class EnvPosixTest : public testing::Test {
|
||||
public:
|
||||
static void SetFileLimits(int read_only_file_limit, int mmap_limit) {
|
||||
EnvPosixTestHelper::SetReadOnlyFDLimit(read_only_file_limit);
|
||||
EnvPosixTestHelper::SetReadOnlyMMapLimit(mmap_limit);
|
||||
}
|
||||
|
||||
EnvPosixTest() : env_(Env::Default()) {}
|
||||
|
||||
Env* env_;
|
||||
};
|
||||
|
||||
TEST_F(EnvPosixTest, TestOpenOnRead) {
|
||||
// Write some test data to a single file that will be opened |n| times.
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string test_file = test_dir + "/open_on_read.txt";
|
||||
|
||||
FILE* f = std::fopen(test_file.c_str(), "we");
|
||||
ASSERT_TRUE(f != nullptr);
|
||||
const char kFileData[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
fputs(kFileData, f);
|
||||
std::fclose(f);
|
||||
|
||||
// Open test file some number above the sum of the two limits to force
|
||||
// open-on-read behavior of POSIX Env leveldb::RandomAccessFile.
|
||||
const int kNumFiles = kReadOnlyFileLimit + kMMapLimit + 5;
|
||||
leveldb::RandomAccessFile* files[kNumFiles] = {0};
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
ASSERT_LEVELDB_OK(env_->NewRandomAccessFile(test_file, &files[i]));
|
||||
}
|
||||
char scratch;
|
||||
Slice read_result;
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
ASSERT_LEVELDB_OK(files[i]->Read(i, 1, &read_result, &scratch));
|
||||
ASSERT_EQ(kFileData[i], read_result[0]);
|
||||
}
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
delete files[i];
|
||||
}
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(test_file));
|
||||
}
|
||||
|
||||
#if HAVE_O_CLOEXEC
|
||||
|
||||
TEST_F(EnvPosixTest, TestCloseOnExecSequentialFile) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string file_path = test_dir + "/close_on_exec_sequential.txt";
|
||||
ASSERT_LEVELDB_OK(WriteStringToFile(env_, "0123456789", file_path));
|
||||
|
||||
leveldb::SequentialFile* file = nullptr;
|
||||
ASSERT_LEVELDB_OK(env_->NewSequentialFile(file_path, &file));
|
||||
CheckCloseOnExecDoesNotLeakFDs(open_fds);
|
||||
delete file;
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
|
||||
}
|
||||
|
||||
TEST_F(EnvPosixTest, TestCloseOnExecRandomAccessFile) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string file_path = test_dir + "/close_on_exec_random_access.txt";
|
||||
ASSERT_LEVELDB_OK(WriteStringToFile(env_, "0123456789", file_path));
|
||||
|
||||
// Exhaust the RandomAccessFile mmap limit. This way, the test
|
||||
// RandomAccessFile instance below is backed by a file descriptor, not by an
|
||||
// mmap region.
|
||||
leveldb::RandomAccessFile* mmapped_files[kReadOnlyFileLimit] = {nullptr};
|
||||
for (int i = 0; i < kReadOnlyFileLimit; i++) {
|
||||
ASSERT_LEVELDB_OK(env_->NewRandomAccessFile(file_path, &mmapped_files[i]));
|
||||
}
|
||||
|
||||
leveldb::RandomAccessFile* file = nullptr;
|
||||
ASSERT_LEVELDB_OK(env_->NewRandomAccessFile(file_path, &file));
|
||||
CheckCloseOnExecDoesNotLeakFDs(open_fds);
|
||||
delete file;
|
||||
|
||||
for (int i = 0; i < kReadOnlyFileLimit; i++) {
|
||||
delete mmapped_files[i];
|
||||
}
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
|
||||
}
|
||||
|
||||
TEST_F(EnvPosixTest, TestCloseOnExecWritableFile) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string file_path = test_dir + "/close_on_exec_writable.txt";
|
||||
ASSERT_LEVELDB_OK(WriteStringToFile(env_, "0123456789", file_path));
|
||||
|
||||
leveldb::WritableFile* file = nullptr;
|
||||
ASSERT_LEVELDB_OK(env_->NewWritableFile(file_path, &file));
|
||||
CheckCloseOnExecDoesNotLeakFDs(open_fds);
|
||||
delete file;
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
|
||||
}
|
||||
|
||||
TEST_F(EnvPosixTest, TestCloseOnExecAppendableFile) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string file_path = test_dir + "/close_on_exec_appendable.txt";
|
||||
ASSERT_LEVELDB_OK(WriteStringToFile(env_, "0123456789", file_path));
|
||||
|
||||
leveldb::WritableFile* file = nullptr;
|
||||
ASSERT_LEVELDB_OK(env_->NewAppendableFile(file_path, &file));
|
||||
CheckCloseOnExecDoesNotLeakFDs(open_fds);
|
||||
delete file;
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
|
||||
}
|
||||
|
||||
TEST_F(EnvPosixTest, TestCloseOnExecLockFile) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string file_path = test_dir + "/close_on_exec_lock.txt";
|
||||
ASSERT_LEVELDB_OK(WriteStringToFile(env_, "0123456789", file_path));
|
||||
|
||||
leveldb::FileLock* lock = nullptr;
|
||||
ASSERT_LEVELDB_OK(env_->LockFile(file_path, &lock));
|
||||
CheckCloseOnExecDoesNotLeakFDs(open_fds);
|
||||
ASSERT_LEVELDB_OK(env_->UnlockFile(lock));
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
|
||||
}
|
||||
|
||||
TEST_F(EnvPosixTest, TestCloseOnExecLogger) {
|
||||
std::unordered_set<int> open_fds;
|
||||
GetOpenFileDescriptors(&open_fds);
|
||||
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string file_path = test_dir + "/close_on_exec_logger.txt";
|
||||
ASSERT_LEVELDB_OK(WriteStringToFile(env_, "0123456789", file_path));
|
||||
|
||||
leveldb::Logger* file = nullptr;
|
||||
ASSERT_LEVELDB_OK(env_->NewLogger(file_path, &file));
|
||||
CheckCloseOnExecDoesNotLeakFDs(open_fds);
|
||||
delete file;
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(file_path));
|
||||
}
|
||||
|
||||
#endif // HAVE_O_CLOEXEC
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
#if HAVE_O_CLOEXEC
|
||||
// Check if we're invoked as a helper program, or as the test suite.
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!std::strcmp(argv[i], kTestCloseOnExecSwitch)) {
|
||||
return TestCloseOnExecHelperMain(argv[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Save argv[0] early, because googletest may modify argv.
|
||||
GetArgvZero()->assign(argv[0], argv[0] + std::strlen(argv[0]) + 1);
|
||||
#endif // HAVE_O_CLOEXEC
|
||||
|
||||
// All tests currently run with the same read-only file limits.
|
||||
leveldb::EnvPosixTest::SetFileLimits(leveldb::kReadOnlyFileLimit,
|
||||
leveldb::kMMapLimit);
|
||||
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_ENV_POSIX_TEST_HELPER_H_
|
||||
#define STORAGE_LEVELDB_UTIL_ENV_POSIX_TEST_HELPER_H_
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class EnvPosixTest;
|
||||
|
||||
// A helper for the POSIX Env to facilitate testing.
|
||||
class EnvPosixTestHelper {
|
||||
private:
|
||||
friend class EnvPosixTest;
|
||||
|
||||
// Set the maximum number of read-only files that will be opened.
|
||||
// Must be called before creating an Env.
|
||||
static void SetReadOnlyFDLimit(int limit);
|
||||
|
||||
// Set the maximum number of read-only files that will be mapped via mmap.
|
||||
// Must be called before creating an Env.
|
||||
static void SetReadOnlyMMapLimit(int limit);
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_ENV_POSIX_TEST_HELPER_H_
|
||||
@@ -0,0 +1,240 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/env.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const int kDelayMicros = 100000;
|
||||
|
||||
class EnvTest : public testing::Test {
|
||||
public:
|
||||
EnvTest() : env_(Env::Default()) {}
|
||||
|
||||
Env* env_;
|
||||
};
|
||||
|
||||
TEST_F(EnvTest, ReadWrite) {
|
||||
Random rnd(test::RandomSeed());
|
||||
|
||||
// Get file to use for testing.
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string test_file_name = test_dir + "/open_on_read.txt";
|
||||
WritableFile* writable_file;
|
||||
ASSERT_LEVELDB_OK(env_->NewWritableFile(test_file_name, &writable_file));
|
||||
|
||||
// Fill a file with data generated via a sequence of randomly sized writes.
|
||||
static const size_t kDataSize = 10 * 1048576;
|
||||
std::string data;
|
||||
while (data.size() < kDataSize) {
|
||||
int len = rnd.Skewed(18); // Up to 2^18 - 1, but typically much smaller
|
||||
std::string r;
|
||||
test::RandomString(&rnd, len, &r);
|
||||
ASSERT_LEVELDB_OK(writable_file->Append(r));
|
||||
data += r;
|
||||
if (rnd.OneIn(10)) {
|
||||
ASSERT_LEVELDB_OK(writable_file->Flush());
|
||||
}
|
||||
}
|
||||
ASSERT_LEVELDB_OK(writable_file->Sync());
|
||||
ASSERT_LEVELDB_OK(writable_file->Close());
|
||||
delete writable_file;
|
||||
|
||||
// Read all data using a sequence of randomly sized reads.
|
||||
SequentialFile* sequential_file;
|
||||
ASSERT_LEVELDB_OK(env_->NewSequentialFile(test_file_name, &sequential_file));
|
||||
std::string read_result;
|
||||
std::string scratch;
|
||||
while (read_result.size() < data.size()) {
|
||||
int len = std::min<int>(rnd.Skewed(18), data.size() - read_result.size());
|
||||
scratch.resize(std::max(len, 1)); // at least 1 so &scratch[0] is legal
|
||||
Slice read;
|
||||
ASSERT_LEVELDB_OK(sequential_file->Read(len, &read, &scratch[0]));
|
||||
if (len > 0) {
|
||||
ASSERT_GT(read.size(), 0);
|
||||
}
|
||||
ASSERT_LE(read.size(), len);
|
||||
read_result.append(read.data(), read.size());
|
||||
}
|
||||
ASSERT_EQ(read_result, data);
|
||||
delete sequential_file;
|
||||
}
|
||||
|
||||
TEST_F(EnvTest, RunImmediately) {
|
||||
struct RunState {
|
||||
port::Mutex mu;
|
||||
port::CondVar cvar{&mu};
|
||||
bool called = false;
|
||||
|
||||
static void Run(void* arg) {
|
||||
RunState* state = reinterpret_cast<RunState*>(arg);
|
||||
MutexLock l(&state->mu);
|
||||
ASSERT_EQ(state->called, false);
|
||||
state->called = true;
|
||||
state->cvar.Signal();
|
||||
}
|
||||
};
|
||||
|
||||
RunState state;
|
||||
env_->Schedule(&RunState::Run, &state);
|
||||
|
||||
MutexLock l(&state.mu);
|
||||
while (!state.called) {
|
||||
state.cvar.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EnvTest, RunMany) {
|
||||
struct RunState {
|
||||
port::Mutex mu;
|
||||
port::CondVar cvar{&mu};
|
||||
int last_id = 0;
|
||||
};
|
||||
|
||||
struct Callback {
|
||||
RunState* state_; // Pointer to shared state.
|
||||
const int id_; // Order# for the execution of this callback.
|
||||
|
||||
Callback(RunState* s, int id) : state_(s), id_(id) {}
|
||||
|
||||
static void Run(void* arg) {
|
||||
Callback* callback = reinterpret_cast<Callback*>(arg);
|
||||
RunState* state = callback->state_;
|
||||
|
||||
MutexLock l(&state->mu);
|
||||
ASSERT_EQ(state->last_id, callback->id_ - 1);
|
||||
state->last_id = callback->id_;
|
||||
state->cvar.Signal();
|
||||
}
|
||||
};
|
||||
|
||||
RunState state;
|
||||
Callback callback1(&state, 1);
|
||||
Callback callback2(&state, 2);
|
||||
Callback callback3(&state, 3);
|
||||
Callback callback4(&state, 4);
|
||||
env_->Schedule(&Callback::Run, &callback1);
|
||||
env_->Schedule(&Callback::Run, &callback2);
|
||||
env_->Schedule(&Callback::Run, &callback3);
|
||||
env_->Schedule(&Callback::Run, &callback4);
|
||||
|
||||
MutexLock l(&state.mu);
|
||||
while (state.last_id != 4) {
|
||||
state.cvar.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
port::Mutex mu;
|
||||
port::CondVar cvar{&mu};
|
||||
|
||||
int val GUARDED_BY(mu);
|
||||
int num_running GUARDED_BY(mu);
|
||||
|
||||
State(int val, int num_running) : val(val), num_running(num_running) {}
|
||||
};
|
||||
|
||||
static void ThreadBody(void* arg) {
|
||||
State* s = reinterpret_cast<State*>(arg);
|
||||
s->mu.Lock();
|
||||
s->val += 1;
|
||||
s->num_running -= 1;
|
||||
s->cvar.Signal();
|
||||
s->mu.Unlock();
|
||||
}
|
||||
|
||||
TEST_F(EnvTest, StartThread) {
|
||||
State state(0, 3);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
env_->StartThread(&ThreadBody, &state);
|
||||
}
|
||||
|
||||
MutexLock l(&state.mu);
|
||||
while (state.num_running != 0) {
|
||||
state.cvar.Wait();
|
||||
}
|
||||
ASSERT_EQ(state.val, 3);
|
||||
}
|
||||
|
||||
TEST_F(EnvTest, TestOpenNonExistentFile) {
|
||||
// Write some test data to a single file that will be opened |n| times.
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
|
||||
std::string non_existent_file = test_dir + "/non_existent_file";
|
||||
ASSERT_TRUE(!env_->FileExists(non_existent_file));
|
||||
|
||||
RandomAccessFile* random_access_file;
|
||||
Status status =
|
||||
env_->NewRandomAccessFile(non_existent_file, &random_access_file);
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
|
||||
SequentialFile* sequential_file;
|
||||
status = env_->NewSequentialFile(non_existent_file, &sequential_file);
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
}
|
||||
|
||||
TEST_F(EnvTest, ReopenWritableFile) {
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string test_file_name = test_dir + "/reopen_writable_file.txt";
|
||||
env_->RemoveFile(test_file_name);
|
||||
|
||||
WritableFile* writable_file;
|
||||
ASSERT_LEVELDB_OK(env_->NewWritableFile(test_file_name, &writable_file));
|
||||
std::string data("hello world!");
|
||||
ASSERT_LEVELDB_OK(writable_file->Append(data));
|
||||
ASSERT_LEVELDB_OK(writable_file->Close());
|
||||
delete writable_file;
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->NewWritableFile(test_file_name, &writable_file));
|
||||
data = "42";
|
||||
ASSERT_LEVELDB_OK(writable_file->Append(data));
|
||||
ASSERT_LEVELDB_OK(writable_file->Close());
|
||||
delete writable_file;
|
||||
|
||||
ASSERT_LEVELDB_OK(ReadFileToString(env_, test_file_name, &data));
|
||||
ASSERT_EQ(std::string("42"), data);
|
||||
env_->RemoveFile(test_file_name);
|
||||
}
|
||||
|
||||
TEST_F(EnvTest, ReopenAppendableFile) {
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string test_file_name = test_dir + "/reopen_appendable_file.txt";
|
||||
env_->RemoveFile(test_file_name);
|
||||
|
||||
WritableFile* appendable_file;
|
||||
ASSERT_LEVELDB_OK(env_->NewAppendableFile(test_file_name, &appendable_file));
|
||||
std::string data("hello world!");
|
||||
ASSERT_LEVELDB_OK(appendable_file->Append(data));
|
||||
ASSERT_LEVELDB_OK(appendable_file->Close());
|
||||
delete appendable_file;
|
||||
|
||||
ASSERT_LEVELDB_OK(env_->NewAppendableFile(test_file_name, &appendable_file));
|
||||
data = "42";
|
||||
ASSERT_LEVELDB_OK(appendable_file->Append(data));
|
||||
ASSERT_LEVELDB_OK(appendable_file->Close());
|
||||
delete appendable_file;
|
||||
|
||||
ASSERT_LEVELDB_OK(ReadFileToString(env_, test_file_name, &data));
|
||||
ASSERT_EQ(std::string("hello world!42"), data);
|
||||
env_->RemoveFile(test_file_name);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,796 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
// Prevent Windows headers from defining min/max macros and instead
|
||||
// use STL.
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif // ifndef NOMINMAX
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
#include "util/env_windows_test_helper.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/windows_logger.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const size_t kWritableFileBufferSize = 65536;
|
||||
|
||||
// Up to 1000 mmaps for 64-bit binaries; none for 32-bit.
|
||||
constexpr int kDefaultMmapLimit = (sizeof(void*) >= 8) ? 1000 : 0;
|
||||
|
||||
// Can be set by by EnvWindowsTestHelper::SetReadOnlyMMapLimit().
|
||||
int g_mmap_limit = kDefaultMmapLimit;
|
||||
|
||||
std::string GetWindowsErrorMessage(DWORD error_code) {
|
||||
std::string message;
|
||||
char* error_text = nullptr;
|
||||
// Use MBCS version of FormatMessage to match return value.
|
||||
size_t error_text_size = ::FormatMessageA(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<char*>(&error_text), 0, nullptr);
|
||||
if (!error_text) {
|
||||
return message;
|
||||
}
|
||||
message.assign(error_text, error_text_size);
|
||||
::LocalFree(error_text);
|
||||
return message;
|
||||
}
|
||||
|
||||
Status WindowsError(const std::string& context, DWORD error_code) {
|
||||
if (error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_PATH_NOT_FOUND)
|
||||
return Status::NotFound(context, GetWindowsErrorMessage(error_code));
|
||||
return Status::IOError(context, GetWindowsErrorMessage(error_code));
|
||||
}
|
||||
|
||||
class ScopedHandle {
|
||||
public:
|
||||
ScopedHandle(HANDLE handle) : handle_(handle) {}
|
||||
ScopedHandle(const ScopedHandle&) = delete;
|
||||
ScopedHandle(ScopedHandle&& other) noexcept : handle_(other.Release()) {}
|
||||
~ScopedHandle() { Close(); }
|
||||
|
||||
ScopedHandle& operator=(const ScopedHandle&) = delete;
|
||||
|
||||
ScopedHandle& operator=(ScopedHandle&& rhs) noexcept {
|
||||
if (this != &rhs) handle_ = rhs.Release();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Close() {
|
||||
if (!is_valid()) {
|
||||
return true;
|
||||
}
|
||||
HANDLE h = handle_;
|
||||
handle_ = INVALID_HANDLE_VALUE;
|
||||
return ::CloseHandle(h);
|
||||
}
|
||||
|
||||
bool is_valid() const {
|
||||
return handle_ != INVALID_HANDLE_VALUE && handle_ != nullptr;
|
||||
}
|
||||
|
||||
HANDLE get() const { return handle_; }
|
||||
|
||||
HANDLE Release() {
|
||||
HANDLE h = handle_;
|
||||
handle_ = INVALID_HANDLE_VALUE;
|
||||
return h;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE handle_;
|
||||
};
|
||||
|
||||
// Helper class to limit resource usage to avoid exhaustion.
|
||||
// Currently used to limit read-only file descriptors and mmap file usage
|
||||
// so that we do not run out of file descriptors or virtual memory, or run into
|
||||
// kernel performance problems for very large databases.
|
||||
class Limiter {
|
||||
public:
|
||||
// Limit maximum number of resources to |max_acquires|.
|
||||
Limiter(int max_acquires) : acquires_allowed_(max_acquires) {}
|
||||
|
||||
Limiter(const Limiter&) = delete;
|
||||
Limiter operator=(const Limiter&) = delete;
|
||||
|
||||
// If another resource is available, acquire it and return true.
|
||||
// Else return false.
|
||||
bool Acquire() {
|
||||
int old_acquires_allowed =
|
||||
acquires_allowed_.fetch_sub(1, std::memory_order_relaxed);
|
||||
|
||||
if (old_acquires_allowed > 0) return true;
|
||||
|
||||
acquires_allowed_.fetch_add(1, std::memory_order_relaxed);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Release a resource acquired by a previous call to Acquire() that returned
|
||||
// true.
|
||||
void Release() { acquires_allowed_.fetch_add(1, std::memory_order_relaxed); }
|
||||
|
||||
private:
|
||||
// The number of available resources.
|
||||
//
|
||||
// This is a counter and is not tied to the invariants of any other class, so
|
||||
// it can be operated on safely using std::memory_order_relaxed.
|
||||
std::atomic<int> acquires_allowed_;
|
||||
};
|
||||
|
||||
class WindowsSequentialFile : public SequentialFile {
|
||||
public:
|
||||
WindowsSequentialFile(std::string filename, ScopedHandle handle)
|
||||
: handle_(std::move(handle)), filename_(std::move(filename)) {}
|
||||
~WindowsSequentialFile() override {}
|
||||
|
||||
Status Read(size_t n, Slice* result, char* scratch) override {
|
||||
DWORD bytes_read;
|
||||
// DWORD is 32-bit, but size_t could technically be larger. However leveldb
|
||||
// files are limited to leveldb::Options::max_file_size which is clamped to
|
||||
// 1<<30 or 1 GiB.
|
||||
assert(n <= std::numeric_limits<DWORD>::max());
|
||||
if (!::ReadFile(handle_.get(), scratch, static_cast<DWORD>(n), &bytes_read,
|
||||
nullptr)) {
|
||||
return WindowsError(filename_, ::GetLastError());
|
||||
}
|
||||
|
||||
*result = Slice(scratch, bytes_read);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status Skip(uint64_t n) override {
|
||||
LARGE_INTEGER distance;
|
||||
distance.QuadPart = n;
|
||||
if (!::SetFilePointerEx(handle_.get(), distance, nullptr, FILE_CURRENT)) {
|
||||
return WindowsError(filename_, ::GetLastError());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
const ScopedHandle handle_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
class WindowsRandomAccessFile : public RandomAccessFile {
|
||||
public:
|
||||
WindowsRandomAccessFile(std::string filename, ScopedHandle handle)
|
||||
: handle_(std::move(handle)), filename_(std::move(filename)) {}
|
||||
|
||||
~WindowsRandomAccessFile() override = default;
|
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
DWORD bytes_read = 0;
|
||||
OVERLAPPED overlapped = {0};
|
||||
|
||||
overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32);
|
||||
overlapped.Offset = static_cast<DWORD>(offset);
|
||||
if (!::ReadFile(handle_.get(), scratch, static_cast<DWORD>(n), &bytes_read,
|
||||
&overlapped)) {
|
||||
DWORD error_code = ::GetLastError();
|
||||
if (error_code != ERROR_HANDLE_EOF) {
|
||||
*result = Slice(scratch, 0);
|
||||
return Status::IOError(filename_, GetWindowsErrorMessage(error_code));
|
||||
}
|
||||
}
|
||||
|
||||
*result = Slice(scratch, bytes_read);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
const ScopedHandle handle_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
class WindowsMmapReadableFile : public RandomAccessFile {
|
||||
public:
|
||||
// base[0,length-1] contains the mmapped contents of the file.
|
||||
WindowsMmapReadableFile(std::string filename, char* mmap_base, size_t length,
|
||||
Limiter* mmap_limiter)
|
||||
: mmap_base_(mmap_base),
|
||||
length_(length),
|
||||
mmap_limiter_(mmap_limiter),
|
||||
filename_(std::move(filename)) {}
|
||||
|
||||
~WindowsMmapReadableFile() override {
|
||||
::UnmapViewOfFile(mmap_base_);
|
||||
mmap_limiter_->Release();
|
||||
}
|
||||
|
||||
Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
if (offset + n > length_) {
|
||||
*result = Slice();
|
||||
return WindowsError(filename_, ERROR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
*result = Slice(mmap_base_ + offset, n);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
char* const mmap_base_;
|
||||
const size_t length_;
|
||||
Limiter* const mmap_limiter_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
class WindowsWritableFile : public WritableFile {
|
||||
public:
|
||||
WindowsWritableFile(std::string filename, ScopedHandle handle)
|
||||
: pos_(0), handle_(std::move(handle)), filename_(std::move(filename)) {}
|
||||
|
||||
~WindowsWritableFile() override = default;
|
||||
|
||||
Status Append(const Slice& data) override {
|
||||
size_t write_size = data.size();
|
||||
const char* write_data = data.data();
|
||||
|
||||
// Fit as much as possible into buffer.
|
||||
size_t copy_size = std::min(write_size, kWritableFileBufferSize - pos_);
|
||||
std::memcpy(buf_ + pos_, write_data, copy_size);
|
||||
write_data += copy_size;
|
||||
write_size -= copy_size;
|
||||
pos_ += copy_size;
|
||||
if (write_size == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Can't fit in buffer, so need to do at least one write.
|
||||
Status status = FlushBuffer();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Small writes go to buffer, large writes are written directly.
|
||||
if (write_size < kWritableFileBufferSize) {
|
||||
std::memcpy(buf_, write_data, write_size);
|
||||
pos_ = write_size;
|
||||
return Status::OK();
|
||||
}
|
||||
return WriteUnbuffered(write_data, write_size);
|
||||
}
|
||||
|
||||
Status Close() override {
|
||||
Status status = FlushBuffer();
|
||||
if (!handle_.Close() && status.ok()) {
|
||||
status = WindowsError(filename_, ::GetLastError());
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Status Flush() override { return FlushBuffer(); }
|
||||
|
||||
Status Sync() override {
|
||||
// On Windows no need to sync parent directory. Its metadata will be updated
|
||||
// via the creation of the new file, without an explicit sync.
|
||||
|
||||
Status status = FlushBuffer();
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!::FlushFileBuffers(handle_.get())) {
|
||||
return Status::IOError(filename_,
|
||||
GetWindowsErrorMessage(::GetLastError()));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
Status FlushBuffer() {
|
||||
Status status = WriteUnbuffered(buf_, pos_);
|
||||
pos_ = 0;
|
||||
return status;
|
||||
}
|
||||
|
||||
Status WriteUnbuffered(const char* data, size_t size) {
|
||||
DWORD bytes_written;
|
||||
if (!::WriteFile(handle_.get(), data, static_cast<DWORD>(size),
|
||||
&bytes_written, nullptr)) {
|
||||
return Status::IOError(filename_,
|
||||
GetWindowsErrorMessage(::GetLastError()));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// buf_[0, pos_-1] contains data to be written to handle_.
|
||||
char buf_[kWritableFileBufferSize];
|
||||
size_t pos_;
|
||||
|
||||
ScopedHandle handle_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
// Lock or unlock the entire file as specified by |lock|. Returns true
|
||||
// when successful, false upon failure. Caller should call ::GetLastError()
|
||||
// to determine cause of failure
|
||||
bool LockOrUnlock(HANDLE handle, bool lock) {
|
||||
if (lock) {
|
||||
return ::LockFile(handle,
|
||||
/*dwFileOffsetLow=*/0, /*dwFileOffsetHigh=*/0,
|
||||
/*nNumberOfBytesToLockLow=*/MAXDWORD,
|
||||
/*nNumberOfBytesToLockHigh=*/MAXDWORD);
|
||||
} else {
|
||||
return ::UnlockFile(handle,
|
||||
/*dwFileOffsetLow=*/0, /*dwFileOffsetHigh=*/0,
|
||||
/*nNumberOfBytesToLockLow=*/MAXDWORD,
|
||||
/*nNumberOfBytesToLockHigh=*/MAXDWORD);
|
||||
}
|
||||
}
|
||||
|
||||
class WindowsFileLock : public FileLock {
|
||||
public:
|
||||
WindowsFileLock(ScopedHandle handle, std::string filename)
|
||||
: handle_(std::move(handle)), filename_(std::move(filename)) {}
|
||||
|
||||
const ScopedHandle& handle() const { return handle_; }
|
||||
const std::string& filename() const { return filename_; }
|
||||
|
||||
private:
|
||||
const ScopedHandle handle_;
|
||||
const std::string filename_;
|
||||
};
|
||||
|
||||
class WindowsEnv : public Env {
|
||||
public:
|
||||
WindowsEnv();
|
||||
~WindowsEnv() override {
|
||||
static const char msg[] =
|
||||
"WindowsEnv singleton destroyed. Unsupported behavior!\n";
|
||||
std::fwrite(msg, 1, sizeof(msg), stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Status NewSequentialFile(const std::string& filename,
|
||||
SequentialFile** result) override {
|
||||
*result = nullptr;
|
||||
DWORD desired_access = GENERIC_READ;
|
||||
DWORD share_mode = FILE_SHARE_READ;
|
||||
ScopedHandle handle = ::CreateFileA(
|
||||
filename.c_str(), desired_access, share_mode,
|
||||
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
|
||||
/*hTemplateFile=*/nullptr);
|
||||
if (!handle.is_valid()) {
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
|
||||
*result = new WindowsSequentialFile(filename, std::move(handle));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewRandomAccessFile(const std::string& filename,
|
||||
RandomAccessFile** result) override {
|
||||
*result = nullptr;
|
||||
DWORD desired_access = GENERIC_READ;
|
||||
DWORD share_mode = FILE_SHARE_READ;
|
||||
ScopedHandle handle =
|
||||
::CreateFileA(filename.c_str(), desired_access, share_mode,
|
||||
/*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_READONLY,
|
||||
/*hTemplateFile=*/nullptr);
|
||||
if (!handle.is_valid()) {
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
if (!mmap_limiter_.Acquire()) {
|
||||
*result = new WindowsRandomAccessFile(filename, std::move(handle));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
LARGE_INTEGER file_size;
|
||||
Status status;
|
||||
if (!::GetFileSizeEx(handle.get(), &file_size)) {
|
||||
mmap_limiter_.Release();
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
|
||||
ScopedHandle mapping =
|
||||
::CreateFileMappingA(handle.get(),
|
||||
/*security attributes=*/nullptr, PAGE_READONLY,
|
||||
/*dwMaximumSizeHigh=*/0,
|
||||
/*dwMaximumSizeLow=*/0,
|
||||
/*lpName=*/nullptr);
|
||||
if (mapping.is_valid()) {
|
||||
void* mmap_base = ::MapViewOfFile(mapping.get(), FILE_MAP_READ,
|
||||
/*dwFileOffsetHigh=*/0,
|
||||
/*dwFileOffsetLow=*/0,
|
||||
/*dwNumberOfBytesToMap=*/0);
|
||||
if (mmap_base) {
|
||||
*result = new WindowsMmapReadableFile(
|
||||
filename, reinterpret_cast<char*>(mmap_base),
|
||||
static_cast<size_t>(file_size.QuadPart), &mmap_limiter_);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
mmap_limiter_.Release();
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& filename,
|
||||
WritableFile** result) override {
|
||||
DWORD desired_access = GENERIC_WRITE;
|
||||
DWORD share_mode = 0; // Exclusive access.
|
||||
ScopedHandle handle = ::CreateFileA(
|
||||
filename.c_str(), desired_access, share_mode,
|
||||
/*lpSecurityAttributes=*/nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
||||
/*hTemplateFile=*/nullptr);
|
||||
if (!handle.is_valid()) {
|
||||
*result = nullptr;
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
|
||||
*result = new WindowsWritableFile(filename, std::move(handle));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewAppendableFile(const std::string& filename,
|
||||
WritableFile** result) override {
|
||||
DWORD desired_access = FILE_APPEND_DATA;
|
||||
DWORD share_mode = 0; // Exclusive access.
|
||||
ScopedHandle handle = ::CreateFileA(
|
||||
filename.c_str(), desired_access, share_mode,
|
||||
/*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
||||
/*hTemplateFile=*/nullptr);
|
||||
if (!handle.is_valid()) {
|
||||
*result = nullptr;
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
|
||||
*result = new WindowsWritableFile(filename, std::move(handle));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool FileExists(const std::string& filename) override {
|
||||
return GetFileAttributesA(filename.c_str()) != INVALID_FILE_ATTRIBUTES;
|
||||
}
|
||||
|
||||
Status GetChildren(const std::string& directory_path,
|
||||
std::vector<std::string>* result) override {
|
||||
const std::string find_pattern = directory_path + "\\*";
|
||||
WIN32_FIND_DATAA find_data;
|
||||
HANDLE dir_handle = ::FindFirstFileA(find_pattern.c_str(), &find_data);
|
||||
if (dir_handle == INVALID_HANDLE_VALUE) {
|
||||
DWORD last_error = ::GetLastError();
|
||||
if (last_error == ERROR_FILE_NOT_FOUND) {
|
||||
return Status::OK();
|
||||
}
|
||||
return WindowsError(directory_path, last_error);
|
||||
}
|
||||
do {
|
||||
char base_name[_MAX_FNAME];
|
||||
char ext[_MAX_EXT];
|
||||
|
||||
if (!_splitpath_s(find_data.cFileName, nullptr, 0, nullptr, 0, base_name,
|
||||
ARRAYSIZE(base_name), ext, ARRAYSIZE(ext))) {
|
||||
result->emplace_back(std::string(base_name) + ext);
|
||||
}
|
||||
} while (::FindNextFileA(dir_handle, &find_data));
|
||||
DWORD last_error = ::GetLastError();
|
||||
::FindClose(dir_handle);
|
||||
if (last_error != ERROR_NO_MORE_FILES) {
|
||||
return WindowsError(directory_path, last_error);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RemoveFile(const std::string& filename) override {
|
||||
if (!::DeleteFileA(filename.c_str())) {
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CreateDir(const std::string& dirname) override {
|
||||
if (!::CreateDirectoryA(dirname.c_str(), nullptr)) {
|
||||
return WindowsError(dirname, ::GetLastError());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RemoveDir(const std::string& dirname) override {
|
||||
if (!::RemoveDirectoryA(dirname.c_str())) {
|
||||
return WindowsError(dirname, ::GetLastError());
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status GetFileSize(const std::string& filename, uint64_t* size) override {
|
||||
WIN32_FILE_ATTRIBUTE_DATA file_attributes;
|
||||
if (!::GetFileAttributesExA(filename.c_str(), GetFileExInfoStandard,
|
||||
&file_attributes)) {
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
}
|
||||
ULARGE_INTEGER file_size;
|
||||
file_size.HighPart = file_attributes.nFileSizeHigh;
|
||||
file_size.LowPart = file_attributes.nFileSizeLow;
|
||||
*size = file_size.QuadPart;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RenameFile(const std::string& from, const std::string& to) override {
|
||||
// Try a simple move first. It will only succeed when |to| doesn't already
|
||||
// exist.
|
||||
if (::MoveFileA(from.c_str(), to.c_str())) {
|
||||
return Status::OK();
|
||||
}
|
||||
DWORD move_error = ::GetLastError();
|
||||
|
||||
// Try the full-blown replace if the move fails, as ReplaceFile will only
|
||||
// succeed when |to| does exist. When writing to a network share, we may not
|
||||
// be able to change the ACLs. Ignore ACL errors then
|
||||
// (REPLACEFILE_IGNORE_MERGE_ERRORS).
|
||||
if (::ReplaceFileA(to.c_str(), from.c_str(), /*lpBackupFileName=*/nullptr,
|
||||
REPLACEFILE_IGNORE_MERGE_ERRORS,
|
||||
/*lpExclude=*/nullptr, /*lpReserved=*/nullptr)) {
|
||||
return Status::OK();
|
||||
}
|
||||
DWORD replace_error = ::GetLastError();
|
||||
// In the case of FILE_ERROR_NOT_FOUND from ReplaceFile, it is likely that
|
||||
// |to| does not exist. In this case, the more relevant error comes from the
|
||||
// call to MoveFile.
|
||||
if (replace_error == ERROR_FILE_NOT_FOUND ||
|
||||
replace_error == ERROR_PATH_NOT_FOUND) {
|
||||
return WindowsError(from, move_error);
|
||||
} else {
|
||||
return WindowsError(from, replace_error);
|
||||
}
|
||||
}
|
||||
|
||||
Status LockFile(const std::string& filename, FileLock** lock) override {
|
||||
*lock = nullptr;
|
||||
Status result;
|
||||
ScopedHandle handle = ::CreateFileA(
|
||||
filename.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
|
||||
/*lpSecurityAttributes=*/nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr);
|
||||
if (!handle.is_valid()) {
|
||||
result = WindowsError(filename, ::GetLastError());
|
||||
} else if (!LockOrUnlock(handle.get(), true)) {
|
||||
result = WindowsError("lock " + filename, ::GetLastError());
|
||||
} else {
|
||||
*lock = new WindowsFileLock(std::move(handle), filename);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status UnlockFile(FileLock* lock) override {
|
||||
WindowsFileLock* windows_file_lock =
|
||||
reinterpret_cast<WindowsFileLock*>(lock);
|
||||
if (!LockOrUnlock(windows_file_lock->handle().get(), false)) {
|
||||
return WindowsError("unlock " + windows_file_lock->filename(),
|
||||
::GetLastError());
|
||||
}
|
||||
delete windows_file_lock;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void Schedule(void (*background_work_function)(void* background_work_arg),
|
||||
void* background_work_arg) override;
|
||||
|
||||
void StartThread(void (*thread_main)(void* thread_main_arg),
|
||||
void* thread_main_arg) override {
|
||||
std::thread new_thread(thread_main, thread_main_arg);
|
||||
new_thread.detach();
|
||||
}
|
||||
|
||||
Status GetTestDirectory(std::string* result) override {
|
||||
const char* env = getenv("TEST_TMPDIR");
|
||||
if (env && env[0] != '\0') {
|
||||
*result = env;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
char tmp_path[MAX_PATH];
|
||||
if (!GetTempPathA(ARRAYSIZE(tmp_path), tmp_path)) {
|
||||
return WindowsError("GetTempPath", ::GetLastError());
|
||||
}
|
||||
std::stringstream ss;
|
||||
ss << tmp_path << "leveldbtest-" << std::this_thread::get_id();
|
||||
*result = ss.str();
|
||||
|
||||
// Directory may already exist
|
||||
CreateDir(*result);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewLogger(const std::string& filename, Logger** result) override {
|
||||
std::FILE* fp = std::fopen(filename.c_str(), "w");
|
||||
if (fp == nullptr) {
|
||||
*result = nullptr;
|
||||
return WindowsError(filename, ::GetLastError());
|
||||
} else {
|
||||
*result = new WindowsLogger(fp);
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t NowMicros() override {
|
||||
// GetSystemTimeAsFileTime typically has a resolution of 10-20 msec.
|
||||
// TODO(cmumford): Switch to GetSystemTimePreciseAsFileTime which is
|
||||
// available in Windows 8 and later.
|
||||
FILETIME ft;
|
||||
::GetSystemTimeAsFileTime(&ft);
|
||||
// Each tick represents a 100-nanosecond intervals since January 1, 1601
|
||||
// (UTC).
|
||||
uint64_t num_ticks =
|
||||
(static_cast<uint64_t>(ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
|
||||
return num_ticks / 10;
|
||||
}
|
||||
|
||||
void SleepForMicroseconds(int micros) override {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
||||
}
|
||||
|
||||
private:
|
||||
void BackgroundThreadMain();
|
||||
|
||||
static void BackgroundThreadEntryPoint(WindowsEnv* env) {
|
||||
env->BackgroundThreadMain();
|
||||
}
|
||||
|
||||
// Stores the work item data in a Schedule() call.
|
||||
//
|
||||
// Instances are constructed on the thread calling Schedule() and used on the
|
||||
// background thread.
|
||||
//
|
||||
// This structure is thread-safe beacuse it is immutable.
|
||||
struct BackgroundWorkItem {
|
||||
explicit BackgroundWorkItem(void (*function)(void* arg), void* arg)
|
||||
: function(function), arg(arg) {}
|
||||
|
||||
void (*const function)(void*);
|
||||
void* const arg;
|
||||
};
|
||||
|
||||
port::Mutex background_work_mutex_;
|
||||
port::CondVar background_work_cv_ GUARDED_BY(background_work_mutex_);
|
||||
bool started_background_thread_ GUARDED_BY(background_work_mutex_);
|
||||
|
||||
std::queue<BackgroundWorkItem> background_work_queue_
|
||||
GUARDED_BY(background_work_mutex_);
|
||||
|
||||
Limiter mmap_limiter_; // Thread-safe.
|
||||
};
|
||||
|
||||
// Return the maximum number of concurrent mmaps.
|
||||
int MaxMmaps() { return g_mmap_limit; }
|
||||
|
||||
WindowsEnv::WindowsEnv()
|
||||
: background_work_cv_(&background_work_mutex_),
|
||||
started_background_thread_(false),
|
||||
mmap_limiter_(MaxMmaps()) {}
|
||||
|
||||
void WindowsEnv::Schedule(
|
||||
void (*background_work_function)(void* background_work_arg),
|
||||
void* background_work_arg) {
|
||||
background_work_mutex_.Lock();
|
||||
|
||||
// Start the background thread, if we haven't done so already.
|
||||
if (!started_background_thread_) {
|
||||
started_background_thread_ = true;
|
||||
std::thread background_thread(WindowsEnv::BackgroundThreadEntryPoint, this);
|
||||
background_thread.detach();
|
||||
}
|
||||
|
||||
// If the queue is empty, the background thread may be waiting for work.
|
||||
if (background_work_queue_.empty()) {
|
||||
background_work_cv_.Signal();
|
||||
}
|
||||
|
||||
background_work_queue_.emplace(background_work_function, background_work_arg);
|
||||
background_work_mutex_.Unlock();
|
||||
}
|
||||
|
||||
void WindowsEnv::BackgroundThreadMain() {
|
||||
while (true) {
|
||||
background_work_mutex_.Lock();
|
||||
|
||||
// Wait until there is work to be done.
|
||||
while (background_work_queue_.empty()) {
|
||||
background_work_cv_.Wait();
|
||||
}
|
||||
|
||||
assert(!background_work_queue_.empty());
|
||||
auto background_work_function = background_work_queue_.front().function;
|
||||
void* background_work_arg = background_work_queue_.front().arg;
|
||||
background_work_queue_.pop();
|
||||
|
||||
background_work_mutex_.Unlock();
|
||||
background_work_function(background_work_arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps an Env instance whose destructor is never created.
|
||||
//
|
||||
// Intended usage:
|
||||
// using PlatformSingletonEnv = SingletonEnv<PlatformEnv>;
|
||||
// void ConfigurePosixEnv(int param) {
|
||||
// PlatformSingletonEnv::AssertEnvNotInitialized();
|
||||
// // set global configuration flags.
|
||||
// }
|
||||
// Env* Env::Default() {
|
||||
// static PlatformSingletonEnv default_env;
|
||||
// return default_env.env();
|
||||
// }
|
||||
template <typename EnvType>
|
||||
class SingletonEnv {
|
||||
public:
|
||||
SingletonEnv() {
|
||||
#if !defined(NDEBUG)
|
||||
env_initialized_.store(true, std::memory_order::memory_order_relaxed);
|
||||
#endif // !defined(NDEBUG)
|
||||
static_assert(sizeof(env_storage_) >= sizeof(EnvType),
|
||||
"env_storage_ will not fit the Env");
|
||||
static_assert(alignof(decltype(env_storage_)) >= alignof(EnvType),
|
||||
"env_storage_ does not meet the Env's alignment needs");
|
||||
new (&env_storage_) EnvType();
|
||||
}
|
||||
~SingletonEnv() = default;
|
||||
|
||||
SingletonEnv(const SingletonEnv&) = delete;
|
||||
SingletonEnv& operator=(const SingletonEnv&) = delete;
|
||||
|
||||
Env* env() { return reinterpret_cast<Env*>(&env_storage_); }
|
||||
|
||||
static void AssertEnvNotInitialized() {
|
||||
#if !defined(NDEBUG)
|
||||
assert(!env_initialized_.load(std::memory_order::memory_order_relaxed));
|
||||
#endif // !defined(NDEBUG)
|
||||
}
|
||||
|
||||
private:
|
||||
typename std::aligned_storage<sizeof(EnvType), alignof(EnvType)>::type
|
||||
env_storage_;
|
||||
#if !defined(NDEBUG)
|
||||
static std::atomic<bool> env_initialized_;
|
||||
#endif // !defined(NDEBUG)
|
||||
};
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
template <typename EnvType>
|
||||
std::atomic<bool> SingletonEnv<EnvType>::env_initialized_;
|
||||
#endif // !defined(NDEBUG)
|
||||
|
||||
using WindowsDefaultEnv = SingletonEnv<WindowsEnv>;
|
||||
|
||||
} // namespace
|
||||
|
||||
void EnvWindowsTestHelper::SetReadOnlyMMapLimit(int limit) {
|
||||
WindowsDefaultEnv::AssertEnvNotInitialized();
|
||||
g_mmap_limit = limit;
|
||||
}
|
||||
|
||||
Env* Env::Default() {
|
||||
static WindowsDefaultEnv env_container;
|
||||
return env_container.env();
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "util/env_windows_test_helper.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
static const int kMMapLimit = 4;
|
||||
|
||||
class EnvWindowsTest : public testing::Test {
|
||||
public:
|
||||
static void SetFileLimits(int mmap_limit) {
|
||||
EnvWindowsTestHelper::SetReadOnlyMMapLimit(mmap_limit);
|
||||
}
|
||||
|
||||
EnvWindowsTest() : env_(Env::Default()) {}
|
||||
|
||||
Env* env_;
|
||||
};
|
||||
|
||||
TEST_F(EnvWindowsTest, TestOpenOnRead) {
|
||||
// Write some test data to a single file that will be opened |n| times.
|
||||
std::string test_dir;
|
||||
ASSERT_LEVELDB_OK(env_->GetTestDirectory(&test_dir));
|
||||
std::string test_file = test_dir + "/open_on_read.txt";
|
||||
|
||||
FILE* f = std::fopen(test_file.c_str(), "w");
|
||||
ASSERT_TRUE(f != nullptr);
|
||||
const char kFileData[] = "abcdefghijklmnopqrstuvwxyz";
|
||||
fputs(kFileData, f);
|
||||
std::fclose(f);
|
||||
|
||||
// Open test file some number above the sum of the two limits to force
|
||||
// leveldb::WindowsEnv to switch from mapping the file into memory
|
||||
// to basic file reading.
|
||||
const int kNumFiles = kMMapLimit + 5;
|
||||
leveldb::RandomAccessFile* files[kNumFiles] = {0};
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
ASSERT_LEVELDB_OK(env_->NewRandomAccessFile(test_file, &files[i]));
|
||||
}
|
||||
char scratch;
|
||||
Slice read_result;
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
ASSERT_LEVELDB_OK(files[i]->Read(i, 1, &read_result, &scratch));
|
||||
ASSERT_EQ(kFileData[i], read_result[0]);
|
||||
}
|
||||
for (int i = 0; i < kNumFiles; i++) {
|
||||
delete files[i];
|
||||
}
|
||||
ASSERT_LEVELDB_OK(env_->RemoveFile(test_file));
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
// All tests currently run with the same read-only file limits.
|
||||
leveldb::EnvWindowsTest::SetFileLimits(leveldb::kMMapLimit);
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright 2018 (c) The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_ENV_WINDOWS_TEST_HELPER_H_
|
||||
#define STORAGE_LEVELDB_UTIL_ENV_WINDOWS_TEST_HELPER_H_
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class EnvWindowsTest;
|
||||
|
||||
// A helper for the Windows Env to facilitate testing.
|
||||
class EnvWindowsTestHelper {
|
||||
private:
|
||||
friend class CorruptionTest;
|
||||
friend class EnvWindowsTest;
|
||||
|
||||
// Set the maximum number of read-only files that will be mapped via mmap.
|
||||
// Must be called before creating an Env.
|
||||
static void SetReadOnlyMMapLimit(int limit);
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_ENV_WINDOWS_TEST_HELPER_H_
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2012 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/filter_policy.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
FilterPolicy::~FilterPolicy() {}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/hash.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "util/coding.h"
|
||||
|
||||
// The FALLTHROUGH_INTENDED macro can be used to annotate implicit fall-through
|
||||
// between switch labels. The real definition should be provided externally.
|
||||
// This one is a fallback version for unsupported compilers.
|
||||
#ifndef FALLTHROUGH_INTENDED
|
||||
#define FALLTHROUGH_INTENDED \
|
||||
do { \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
uint32_t Hash(const char* data, size_t n, uint32_t seed) {
|
||||
// Similar to murmur hash
|
||||
const uint32_t m = 0xc6a4a793;
|
||||
const uint32_t r = 24;
|
||||
const char* limit = data + n;
|
||||
uint32_t h = seed ^ (n * m);
|
||||
|
||||
// Pick up four bytes at a time
|
||||
while (data + 4 <= limit) {
|
||||
uint32_t w = DecodeFixed32(data);
|
||||
data += 4;
|
||||
h += w;
|
||||
h *= m;
|
||||
h ^= (h >> 16);
|
||||
}
|
||||
|
||||
// Pick up remaining bytes
|
||||
switch (limit - data) {
|
||||
case 3:
|
||||
h += static_cast<uint8_t>(data[2]) << 16;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
h += static_cast<uint8_t>(data[1]) << 8;
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 1:
|
||||
h += static_cast<uint8_t>(data[0]);
|
||||
h *= m;
|
||||
h ^= (h >> r);
|
||||
break;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// Simple hash function used for internal data structures
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_HASH_H_
|
||||
#define STORAGE_LEVELDB_UTIL_HASH_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
uint32_t Hash(const char* data, size_t n, uint32_t seed);
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_HASH_H_
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/hash.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
TEST(HASH, SignedUnsignedIssue) {
|
||||
const uint8_t data1[1] = {0x62};
|
||||
const uint8_t data2[2] = {0xc3, 0x97};
|
||||
const uint8_t data3[3] = {0xe2, 0x99, 0xa5};
|
||||
const uint8_t data4[4] = {0xe1, 0x80, 0xb9, 0x32};
|
||||
const uint8_t data5[48] = {
|
||||
0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
ASSERT_EQ(Hash(0, 0, 0xbc9f1d34), 0xbc9f1d34);
|
||||
ASSERT_EQ(
|
||||
Hash(reinterpret_cast<const char*>(data1), sizeof(data1), 0xbc9f1d34),
|
||||
0xef1345c4);
|
||||
ASSERT_EQ(
|
||||
Hash(reinterpret_cast<const char*>(data2), sizeof(data2), 0xbc9f1d34),
|
||||
0x5b663814);
|
||||
ASSERT_EQ(
|
||||
Hash(reinterpret_cast<const char*>(data3), sizeof(data3), 0xbc9f1d34),
|
||||
0x323c078f);
|
||||
ASSERT_EQ(
|
||||
Hash(reinterpret_cast<const char*>(data4), sizeof(data4), 0xbc9f1d34),
|
||||
0xed21633a);
|
||||
ASSERT_EQ(
|
||||
Hash(reinterpret_cast<const char*>(data5), sizeof(data5), 0x12345678),
|
||||
0xf333dabb);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/histogram.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
#include "port/port.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
const double Histogram::kBucketLimit[kNumBuckets] = {
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
12,
|
||||
14,
|
||||
16,
|
||||
18,
|
||||
20,
|
||||
25,
|
||||
30,
|
||||
35,
|
||||
40,
|
||||
45,
|
||||
50,
|
||||
60,
|
||||
70,
|
||||
80,
|
||||
90,
|
||||
100,
|
||||
120,
|
||||
140,
|
||||
160,
|
||||
180,
|
||||
200,
|
||||
250,
|
||||
300,
|
||||
350,
|
||||
400,
|
||||
450,
|
||||
500,
|
||||
600,
|
||||
700,
|
||||
800,
|
||||
900,
|
||||
1000,
|
||||
1200,
|
||||
1400,
|
||||
1600,
|
||||
1800,
|
||||
2000,
|
||||
2500,
|
||||
3000,
|
||||
3500,
|
||||
4000,
|
||||
4500,
|
||||
5000,
|
||||
6000,
|
||||
7000,
|
||||
8000,
|
||||
9000,
|
||||
10000,
|
||||
12000,
|
||||
14000,
|
||||
16000,
|
||||
18000,
|
||||
20000,
|
||||
25000,
|
||||
30000,
|
||||
35000,
|
||||
40000,
|
||||
45000,
|
||||
50000,
|
||||
60000,
|
||||
70000,
|
||||
80000,
|
||||
90000,
|
||||
100000,
|
||||
120000,
|
||||
140000,
|
||||
160000,
|
||||
180000,
|
||||
200000,
|
||||
250000,
|
||||
300000,
|
||||
350000,
|
||||
400000,
|
||||
450000,
|
||||
500000,
|
||||
600000,
|
||||
700000,
|
||||
800000,
|
||||
900000,
|
||||
1000000,
|
||||
1200000,
|
||||
1400000,
|
||||
1600000,
|
||||
1800000,
|
||||
2000000,
|
||||
2500000,
|
||||
3000000,
|
||||
3500000,
|
||||
4000000,
|
||||
4500000,
|
||||
5000000,
|
||||
6000000,
|
||||
7000000,
|
||||
8000000,
|
||||
9000000,
|
||||
10000000,
|
||||
12000000,
|
||||
14000000,
|
||||
16000000,
|
||||
18000000,
|
||||
20000000,
|
||||
25000000,
|
||||
30000000,
|
||||
35000000,
|
||||
40000000,
|
||||
45000000,
|
||||
50000000,
|
||||
60000000,
|
||||
70000000,
|
||||
80000000,
|
||||
90000000,
|
||||
100000000,
|
||||
120000000,
|
||||
140000000,
|
||||
160000000,
|
||||
180000000,
|
||||
200000000,
|
||||
250000000,
|
||||
300000000,
|
||||
350000000,
|
||||
400000000,
|
||||
450000000,
|
||||
500000000,
|
||||
600000000,
|
||||
700000000,
|
||||
800000000,
|
||||
900000000,
|
||||
1000000000,
|
||||
1200000000,
|
||||
1400000000,
|
||||
1600000000,
|
||||
1800000000,
|
||||
2000000000,
|
||||
2500000000.0,
|
||||
3000000000.0,
|
||||
3500000000.0,
|
||||
4000000000.0,
|
||||
4500000000.0,
|
||||
5000000000.0,
|
||||
6000000000.0,
|
||||
7000000000.0,
|
||||
8000000000.0,
|
||||
9000000000.0,
|
||||
1e200,
|
||||
};
|
||||
|
||||
void Histogram::Clear() {
|
||||
min_ = kBucketLimit[kNumBuckets - 1];
|
||||
max_ = 0;
|
||||
num_ = 0;
|
||||
sum_ = 0;
|
||||
sum_squares_ = 0;
|
||||
for (int i = 0; i < kNumBuckets; i++) {
|
||||
buckets_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Histogram::Add(double value) {
|
||||
// Linear search is fast enough for our usage in db_bench
|
||||
int b = 0;
|
||||
while (b < kNumBuckets - 1 && kBucketLimit[b] <= value) {
|
||||
b++;
|
||||
}
|
||||
buckets_[b] += 1.0;
|
||||
if (min_ > value) min_ = value;
|
||||
if (max_ < value) max_ = value;
|
||||
num_++;
|
||||
sum_ += value;
|
||||
sum_squares_ += (value * value);
|
||||
}
|
||||
|
||||
void Histogram::Merge(const Histogram& other) {
|
||||
if (other.min_ < min_) min_ = other.min_;
|
||||
if (other.max_ > max_) max_ = other.max_;
|
||||
num_ += other.num_;
|
||||
sum_ += other.sum_;
|
||||
sum_squares_ += other.sum_squares_;
|
||||
for (int b = 0; b < kNumBuckets; b++) {
|
||||
buckets_[b] += other.buckets_[b];
|
||||
}
|
||||
}
|
||||
|
||||
double Histogram::Median() const { return Percentile(50.0); }
|
||||
|
||||
double Histogram::Percentile(double p) const {
|
||||
double threshold = num_ * (p / 100.0);
|
||||
double sum = 0;
|
||||
for (int b = 0; b < kNumBuckets; b++) {
|
||||
sum += buckets_[b];
|
||||
if (sum >= threshold) {
|
||||
// Scale linearly within this bucket
|
||||
double left_point = (b == 0) ? 0 : kBucketLimit[b - 1];
|
||||
double right_point = kBucketLimit[b];
|
||||
double left_sum = sum - buckets_[b];
|
||||
double right_sum = sum;
|
||||
double pos = (threshold - left_sum) / (right_sum - left_sum);
|
||||
double r = left_point + (right_point - left_point) * pos;
|
||||
if (r < min_) r = min_;
|
||||
if (r > max_) r = max_;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return max_;
|
||||
}
|
||||
|
||||
double Histogram::Average() const {
|
||||
if (num_ == 0.0) return 0;
|
||||
return sum_ / num_;
|
||||
}
|
||||
|
||||
double Histogram::StandardDeviation() const {
|
||||
if (num_ == 0.0) return 0;
|
||||
double variance = (sum_squares_ * num_ - sum_ * sum_) / (num_ * num_);
|
||||
return sqrt(variance);
|
||||
}
|
||||
|
||||
std::string Histogram::ToString() const {
|
||||
std::string r;
|
||||
char buf[200];
|
||||
std::snprintf(buf, sizeof(buf), "Count: %.0f Average: %.4f StdDev: %.2f\n",
|
||||
num_, Average(), StandardDeviation());
|
||||
r.append(buf);
|
||||
std::snprintf(buf, sizeof(buf), "Min: %.4f Median: %.4f Max: %.4f\n",
|
||||
(num_ == 0.0 ? 0.0 : min_), Median(), max_);
|
||||
r.append(buf);
|
||||
r.append("------------------------------------------------------\n");
|
||||
const double mult = 100.0 / num_;
|
||||
double sum = 0;
|
||||
for (int b = 0; b < kNumBuckets; b++) {
|
||||
if (buckets_[b] <= 0.0) continue;
|
||||
sum += buckets_[b];
|
||||
std::snprintf(buf, sizeof(buf), "[ %7.0f, %7.0f ) %7.0f %7.3f%% %7.3f%% ",
|
||||
((b == 0) ? 0.0 : kBucketLimit[b - 1]), // left
|
||||
kBucketLimit[b], // right
|
||||
buckets_[b], // count
|
||||
mult * buckets_[b], // percentage
|
||||
mult * sum); // cumulative percentage
|
||||
r.append(buf);
|
||||
|
||||
// Add hash marks based on percentage; 20 marks for 100%.
|
||||
int marks = static_cast<int>(20 * (buckets_[b] / num_) + 0.5);
|
||||
r.append(marks, '#');
|
||||
r.push_back('\n');
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_HISTOGRAM_H_
|
||||
#define STORAGE_LEVELDB_UTIL_HISTOGRAM_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Histogram {
|
||||
public:
|
||||
Histogram() {}
|
||||
~Histogram() {}
|
||||
|
||||
void Clear();
|
||||
void Add(double value);
|
||||
void Merge(const Histogram& other);
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
enum { kNumBuckets = 154 };
|
||||
|
||||
double Median() const;
|
||||
double Percentile(double p) const;
|
||||
double Average() const;
|
||||
double StandardDeviation() const;
|
||||
|
||||
static const double kBucketLimit[kNumBuckets];
|
||||
|
||||
double min_;
|
||||
double max_;
|
||||
double num_;
|
||||
double sum_;
|
||||
double sum_squares_;
|
||||
|
||||
double buckets_[kNumBuckets];
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_HISTOGRAM_H_
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/slice.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
void AppendNumberTo(std::string* str, uint64_t num) {
|
||||
char buf[30];
|
||||
std::snprintf(buf, sizeof(buf), "%llu", static_cast<unsigned long long>(num));
|
||||
str->append(buf);
|
||||
}
|
||||
|
||||
void AppendEscapedStringTo(std::string* str, const Slice& value) {
|
||||
for (size_t i = 0; i < value.size(); i++) {
|
||||
char c = value[i];
|
||||
if (c >= ' ' && c <= '~') {
|
||||
str->push_back(c);
|
||||
} else {
|
||||
char buf[10];
|
||||
std::snprintf(buf, sizeof(buf), "\\x%02x",
|
||||
static_cast<unsigned int>(c) & 0xff);
|
||||
str->append(buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string NumberToString(uint64_t num) {
|
||||
std::string r;
|
||||
AppendNumberTo(&r, num);
|
||||
return r;
|
||||
}
|
||||
|
||||
std::string EscapeString(const Slice& value) {
|
||||
std::string r;
|
||||
AppendEscapedStringTo(&r, value);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool ConsumeDecimalNumber(Slice* in, uint64_t* val) {
|
||||
// Constants that will be optimized away.
|
||||
constexpr const uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
|
||||
constexpr const char kLastDigitOfMaxUint64 =
|
||||
'0' + static_cast<char>(kMaxUint64 % 10);
|
||||
|
||||
uint64_t value = 0;
|
||||
|
||||
// reinterpret_cast-ing from char* to uint8_t* to avoid signedness.
|
||||
const uint8_t* start = reinterpret_cast<const uint8_t*>(in->data());
|
||||
|
||||
const uint8_t* end = start + in->size();
|
||||
const uint8_t* current = start;
|
||||
for (; current != end; ++current) {
|
||||
const uint8_t ch = *current;
|
||||
if (ch < '0' || ch > '9') break;
|
||||
|
||||
// Overflow check.
|
||||
// kMaxUint64 / 10 is also constant and will be optimized away.
|
||||
if (value > kMaxUint64 / 10 ||
|
||||
(value == kMaxUint64 / 10 && ch > kLastDigitOfMaxUint64)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value = (value * 10) + (ch - '0');
|
||||
}
|
||||
|
||||
*val = value;
|
||||
const size_t digits_consumed = current - start;
|
||||
in->remove_prefix(digits_consumed);
|
||||
return digits_consumed != 0;
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// Must not be included from any .h files to avoid polluting the namespace
|
||||
// with macros.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_LOGGING_H_
|
||||
#define STORAGE_LEVELDB_UTIL_LOGGING_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "port/port.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Slice;
|
||||
class WritableFile;
|
||||
|
||||
// Append a human-readable printout of "num" to *str
|
||||
void AppendNumberTo(std::string* str, uint64_t num);
|
||||
|
||||
// Append a human-readable printout of "value" to *str.
|
||||
// Escapes any non-printable characters found in "value".
|
||||
void AppendEscapedStringTo(std::string* str, const Slice& value);
|
||||
|
||||
// Return a human-readable printout of "num"
|
||||
std::string NumberToString(uint64_t num);
|
||||
|
||||
// Return a human-readable version of "value".
|
||||
// Escapes any non-printable characters found in "value".
|
||||
std::string EscapeString(const Slice& value);
|
||||
|
||||
// Parse a human-readable number from "*in" into *value. On success,
|
||||
// advances "*in" past the consumed number and sets "*val" to the
|
||||
// numeric value. Otherwise, returns false and leaves *in in an
|
||||
// unspecified state.
|
||||
bool ConsumeDecimalNumber(Slice* in, uint64_t* val);
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_LOGGING_H_
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/logging.h"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "leveldb/slice.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
TEST(Logging, NumberToString) {
|
||||
ASSERT_EQ("0", NumberToString(0));
|
||||
ASSERT_EQ("1", NumberToString(1));
|
||||
ASSERT_EQ("9", NumberToString(9));
|
||||
|
||||
ASSERT_EQ("10", NumberToString(10));
|
||||
ASSERT_EQ("11", NumberToString(11));
|
||||
ASSERT_EQ("19", NumberToString(19));
|
||||
ASSERT_EQ("99", NumberToString(99));
|
||||
|
||||
ASSERT_EQ("100", NumberToString(100));
|
||||
ASSERT_EQ("109", NumberToString(109));
|
||||
ASSERT_EQ("190", NumberToString(190));
|
||||
ASSERT_EQ("123", NumberToString(123));
|
||||
ASSERT_EQ("12345678", NumberToString(12345678));
|
||||
|
||||
static_assert(std::numeric_limits<uint64_t>::max() == 18446744073709551615U,
|
||||
"Test consistency check");
|
||||
ASSERT_EQ("18446744073709551000", NumberToString(18446744073709551000U));
|
||||
ASSERT_EQ("18446744073709551600", NumberToString(18446744073709551600U));
|
||||
ASSERT_EQ("18446744073709551610", NumberToString(18446744073709551610U));
|
||||
ASSERT_EQ("18446744073709551614", NumberToString(18446744073709551614U));
|
||||
ASSERT_EQ("18446744073709551615", NumberToString(18446744073709551615U));
|
||||
}
|
||||
|
||||
void ConsumeDecimalNumberRoundtripTest(uint64_t number,
|
||||
const std::string& padding = "") {
|
||||
std::string decimal_number = NumberToString(number);
|
||||
std::string input_string = decimal_number + padding;
|
||||
Slice input(input_string);
|
||||
Slice output = input;
|
||||
uint64_t result;
|
||||
ASSERT_TRUE(ConsumeDecimalNumber(&output, &result));
|
||||
ASSERT_EQ(number, result);
|
||||
ASSERT_EQ(decimal_number.size(), output.data() - input.data());
|
||||
ASSERT_EQ(padding.size(), output.size());
|
||||
}
|
||||
|
||||
TEST(Logging, ConsumeDecimalNumberRoundtrip) {
|
||||
ConsumeDecimalNumberRoundtripTest(0);
|
||||
ConsumeDecimalNumberRoundtripTest(1);
|
||||
ConsumeDecimalNumberRoundtripTest(9);
|
||||
|
||||
ConsumeDecimalNumberRoundtripTest(10);
|
||||
ConsumeDecimalNumberRoundtripTest(11);
|
||||
ConsumeDecimalNumberRoundtripTest(19);
|
||||
ConsumeDecimalNumberRoundtripTest(99);
|
||||
|
||||
ConsumeDecimalNumberRoundtripTest(100);
|
||||
ConsumeDecimalNumberRoundtripTest(109);
|
||||
ConsumeDecimalNumberRoundtripTest(190);
|
||||
ConsumeDecimalNumberRoundtripTest(123);
|
||||
ASSERT_EQ("12345678", NumberToString(12345678));
|
||||
|
||||
for (uint64_t i = 0; i < 100; ++i) {
|
||||
uint64_t large_number = std::numeric_limits<uint64_t>::max() - i;
|
||||
ConsumeDecimalNumberRoundtripTest(large_number);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Logging, ConsumeDecimalNumberRoundtripWithPadding) {
|
||||
ConsumeDecimalNumberRoundtripTest(0, " ");
|
||||
ConsumeDecimalNumberRoundtripTest(1, "abc");
|
||||
ConsumeDecimalNumberRoundtripTest(9, "x");
|
||||
|
||||
ConsumeDecimalNumberRoundtripTest(10, "_");
|
||||
ConsumeDecimalNumberRoundtripTest(11, std::string("\0\0\0", 3));
|
||||
ConsumeDecimalNumberRoundtripTest(19, "abc");
|
||||
ConsumeDecimalNumberRoundtripTest(99, "padding");
|
||||
|
||||
ConsumeDecimalNumberRoundtripTest(100, " ");
|
||||
|
||||
for (uint64_t i = 0; i < 100; ++i) {
|
||||
uint64_t large_number = std::numeric_limits<uint64_t>::max() - i;
|
||||
ConsumeDecimalNumberRoundtripTest(large_number, "pad");
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumeDecimalNumberOverflowTest(const std::string& input_string) {
|
||||
Slice input(input_string);
|
||||
Slice output = input;
|
||||
uint64_t result;
|
||||
ASSERT_EQ(false, ConsumeDecimalNumber(&output, &result));
|
||||
}
|
||||
|
||||
TEST(Logging, ConsumeDecimalNumberOverflow) {
|
||||
static_assert(std::numeric_limits<uint64_t>::max() == 18446744073709551615U,
|
||||
"Test consistency check");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551616");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551617");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551618");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551619");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551620");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551621");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551622");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551623");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551624");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551625");
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551626");
|
||||
|
||||
ConsumeDecimalNumberOverflowTest("18446744073709551700");
|
||||
|
||||
ConsumeDecimalNumberOverflowTest("99999999999999999999");
|
||||
}
|
||||
|
||||
void ConsumeDecimalNumberNoDigitsTest(const std::string& input_string) {
|
||||
Slice input(input_string);
|
||||
Slice output = input;
|
||||
uint64_t result;
|
||||
ASSERT_EQ(false, ConsumeDecimalNumber(&output, &result));
|
||||
ASSERT_EQ(input.data(), output.data());
|
||||
ASSERT_EQ(input.size(), output.size());
|
||||
}
|
||||
|
||||
TEST(Logging, ConsumeDecimalNumberNoDigits) {
|
||||
ConsumeDecimalNumberNoDigitsTest("");
|
||||
ConsumeDecimalNumberNoDigitsTest(" ");
|
||||
ConsumeDecimalNumberNoDigitsTest("a");
|
||||
ConsumeDecimalNumberNoDigitsTest(" 123");
|
||||
ConsumeDecimalNumberNoDigitsTest("a123");
|
||||
ConsumeDecimalNumberNoDigitsTest(std::string("\000123", 4));
|
||||
ConsumeDecimalNumberNoDigitsTest(std::string("\177123", 4));
|
||||
ConsumeDecimalNumberNoDigitsTest(std::string("\377123", 4));
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_MUTEXLOCK_H_
|
||||
#define STORAGE_LEVELDB_UTIL_MUTEXLOCK_H_
|
||||
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Helper class that locks a mutex on construction and unlocks the mutex when
|
||||
// the destructor of the MutexLock object is invoked.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// void MyClass::MyMethod() {
|
||||
// MutexLock l(&mu_); // mu_ is an instance variable
|
||||
// ... some complex code, possibly with multiple return paths ...
|
||||
// }
|
||||
|
||||
class SCOPED_LOCKABLE MutexLock {
|
||||
public:
|
||||
explicit MutexLock(port::Mutex* mu) EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) {
|
||||
this->mu_->Lock();
|
||||
}
|
||||
~MutexLock() UNLOCK_FUNCTION() { this->mu_->Unlock(); }
|
||||
|
||||
MutexLock(const MutexLock&) = delete;
|
||||
MutexLock& operator=(const MutexLock&) = delete;
|
||||
|
||||
private:
|
||||
port::Mutex* const mu_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_MUTEXLOCK_H_
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_NO_DESTRUCTOR_H_
|
||||
#define STORAGE_LEVELDB_UTIL_NO_DESTRUCTOR_H_
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// Wraps an instance whose destructor is never called.
|
||||
//
|
||||
// This is intended for use with function-level static variables.
|
||||
template <typename InstanceType>
|
||||
class NoDestructor {
|
||||
public:
|
||||
template <typename... ConstructorArgTypes>
|
||||
explicit NoDestructor(ConstructorArgTypes&&... constructor_args) {
|
||||
static_assert(sizeof(instance_storage_) >= sizeof(InstanceType),
|
||||
"instance_storage_ is not large enough to hold the instance");
|
||||
static_assert(
|
||||
alignof(decltype(instance_storage_)) >= alignof(InstanceType),
|
||||
"instance_storage_ does not meet the instance's alignment requirement");
|
||||
new (&instance_storage_)
|
||||
InstanceType(std::forward<ConstructorArgTypes>(constructor_args)...);
|
||||
}
|
||||
|
||||
~NoDestructor() = default;
|
||||
|
||||
NoDestructor(const NoDestructor&) = delete;
|
||||
NoDestructor& operator=(const NoDestructor&) = delete;
|
||||
|
||||
InstanceType* get() {
|
||||
return reinterpret_cast<InstanceType*>(&instance_storage_);
|
||||
}
|
||||
|
||||
private:
|
||||
typename std::aligned_storage<sizeof(InstanceType),
|
||||
alignof(InstanceType)>::type instance_storage_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_NO_DESTRUCTOR_H_
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/no_destructor.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
|
||||
struct DoNotDestruct {
|
||||
public:
|
||||
DoNotDestruct(uint32_t a, uint64_t b) : a(a), b(b) {}
|
||||
~DoNotDestruct() { std::abort(); }
|
||||
|
||||
// Used to check constructor argument forwarding.
|
||||
uint32_t a;
|
||||
uint64_t b;
|
||||
};
|
||||
|
||||
constexpr const uint32_t kGoldenA = 0xdeadbeef;
|
||||
constexpr const uint64_t kGoldenB = 0xaabbccddeeffaabb;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(NoDestructorTest, StackInstance) {
|
||||
NoDestructor<DoNotDestruct> instance(kGoldenA, kGoldenB);
|
||||
ASSERT_EQ(kGoldenA, instance.get()->a);
|
||||
ASSERT_EQ(kGoldenB, instance.get()->b);
|
||||
}
|
||||
|
||||
TEST(NoDestructorTest, StaticInstance) {
|
||||
static NoDestructor<DoNotDestruct> instance(kGoldenA, kGoldenB);
|
||||
ASSERT_EQ(kGoldenA, instance.get()->a);
|
||||
ASSERT_EQ(kGoldenB, instance.get()->b);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/options.h"
|
||||
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/env.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
Options::Options() : comparator(BytewiseComparator()), env(nullptr) {}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// Logger implementation that can be shared by all environments
|
||||
// where enough posix functionality is available.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
|
||||
#define STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "leveldb/env.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class PosixLogger final : public Logger {
|
||||
public:
|
||||
// Creates a logger that writes to the given file.
|
||||
//
|
||||
// The PosixLogger instance takes ownership of the file handle.
|
||||
explicit PosixLogger(std::FILE* fp) : fp_(fp) { assert(fp != nullptr); }
|
||||
|
||||
~PosixLogger() override { std::fclose(fp_); }
|
||||
|
||||
void Logv(const char* format, std::va_list arguments) override {
|
||||
// Record the time as close to the Logv() call as possible.
|
||||
struct ::timeval now_timeval;
|
||||
::gettimeofday(&now_timeval, nullptr);
|
||||
const std::time_t now_seconds = now_timeval.tv_sec;
|
||||
struct std::tm now_components;
|
||||
::localtime_r(&now_seconds, &now_components);
|
||||
|
||||
// Record the thread ID.
|
||||
constexpr const int kMaxThreadIdSize = 32;
|
||||
std::ostringstream thread_stream;
|
||||
thread_stream << std::this_thread::get_id();
|
||||
std::string thread_id = thread_stream.str();
|
||||
if (thread_id.size() > kMaxThreadIdSize) {
|
||||
thread_id.resize(kMaxThreadIdSize);
|
||||
}
|
||||
|
||||
// We first attempt to print into a stack-allocated buffer. If this attempt
|
||||
// fails, we make a second attempt with a dynamically allocated buffer.
|
||||
constexpr const int kStackBufferSize = 512;
|
||||
char stack_buffer[kStackBufferSize];
|
||||
static_assert(sizeof(stack_buffer) == static_cast<size_t>(kStackBufferSize),
|
||||
"sizeof(char) is expected to be 1 in C++");
|
||||
|
||||
int dynamic_buffer_size = 0; // Computed in the first iteration.
|
||||
for (int iteration = 0; iteration < 2; ++iteration) {
|
||||
const int buffer_size =
|
||||
(iteration == 0) ? kStackBufferSize : dynamic_buffer_size;
|
||||
char* const buffer =
|
||||
(iteration == 0) ? stack_buffer : new char[dynamic_buffer_size];
|
||||
|
||||
// Print the header into the buffer.
|
||||
int buffer_offset = std::snprintf(
|
||||
buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
|
||||
now_components.tm_year + 1900, now_components.tm_mon + 1,
|
||||
now_components.tm_mday, now_components.tm_hour, now_components.tm_min,
|
||||
now_components.tm_sec, static_cast<int>(now_timeval.tv_usec),
|
||||
thread_id.c_str());
|
||||
|
||||
// The header can be at most 28 characters (10 date + 15 time +
|
||||
// 3 delimiters) plus the thread ID, which should fit comfortably into the
|
||||
// static buffer.
|
||||
assert(buffer_offset <= 28 + kMaxThreadIdSize);
|
||||
static_assert(28 + kMaxThreadIdSize < kStackBufferSize,
|
||||
"stack-allocated buffer may not fit the message header");
|
||||
assert(buffer_offset < buffer_size);
|
||||
|
||||
// Print the message into the buffer.
|
||||
std::va_list arguments_copy;
|
||||
va_copy(arguments_copy, arguments);
|
||||
buffer_offset +=
|
||||
std::vsnprintf(buffer + buffer_offset, buffer_size - buffer_offset,
|
||||
format, arguments_copy);
|
||||
va_end(arguments_copy);
|
||||
|
||||
// The code below may append a newline at the end of the buffer, which
|
||||
// requires an extra character.
|
||||
if (buffer_offset >= buffer_size - 1) {
|
||||
// The message did not fit into the buffer.
|
||||
if (iteration == 0) {
|
||||
// Re-run the loop and use a dynamically-allocated buffer. The buffer
|
||||
// will be large enough for the log message, an extra newline and a
|
||||
// null terminator.
|
||||
dynamic_buffer_size = buffer_offset + 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The dynamically-allocated buffer was incorrectly sized. This should
|
||||
// not happen, assuming a correct implementation of std::(v)snprintf.
|
||||
// Fail in tests, recover by truncating the log message in production.
|
||||
assert(false);
|
||||
buffer_offset = buffer_size - 1;
|
||||
}
|
||||
|
||||
// Add a newline if necessary.
|
||||
if (buffer[buffer_offset - 1] != '\n') {
|
||||
buffer[buffer_offset] = '\n';
|
||||
++buffer_offset;
|
||||
}
|
||||
|
||||
assert(buffer_offset <= buffer_size);
|
||||
std::fwrite(buffer, 1, buffer_offset, fp_);
|
||||
std::fflush(fp_);
|
||||
|
||||
if (iteration != 0) {
|
||||
delete[] buffer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* const fp_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_POSIX_LOGGER_H_
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_RANDOM_H_
|
||||
#define STORAGE_LEVELDB_UTIL_RANDOM_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
// A very simple random number generator. Not especially good at
|
||||
// generating truly random bits, but good enough for our needs in this
|
||||
// package.
|
||||
class Random {
|
||||
private:
|
||||
uint32_t seed_;
|
||||
|
||||
public:
|
||||
explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) {
|
||||
// Avoid bad seeds.
|
||||
if (seed_ == 0 || seed_ == 2147483647L) {
|
||||
seed_ = 1;
|
||||
}
|
||||
}
|
||||
uint32_t Next() {
|
||||
static const uint32_t M = 2147483647L; // 2^31-1
|
||||
static const uint64_t A = 16807; // bits 14, 8, 7, 5, 2, 1, 0
|
||||
// We are computing
|
||||
// seed_ = (seed_ * A) % M, where M = 2^31-1
|
||||
//
|
||||
// seed_ must not be zero or M, or else all subsequent computed values
|
||||
// will be zero or M respectively. For all other values, seed_ will end
|
||||
// up cycling through every number in [1,M-1]
|
||||
uint64_t product = seed_ * A;
|
||||
|
||||
// Compute (product % M) using the fact that ((x << 31) % M) == x.
|
||||
seed_ = static_cast<uint32_t>((product >> 31) + (product & M));
|
||||
// The first reduction may overflow by 1 bit, so we may need to
|
||||
// repeat. mod == M is not possible; using > allows the faster
|
||||
// sign-bit-based test.
|
||||
if (seed_ > M) {
|
||||
seed_ -= M;
|
||||
}
|
||||
return seed_;
|
||||
}
|
||||
// Returns a uniformly distributed value in the range [0..n-1]
|
||||
// REQUIRES: n > 0
|
||||
uint32_t Uniform(int n) { return Next() % n; }
|
||||
|
||||
// Randomly returns true ~"1/n" of the time, and false otherwise.
|
||||
// REQUIRES: n > 0
|
||||
bool OneIn(int n) { return (Next() % n) == 0; }
|
||||
|
||||
// Skewed: pick "base" uniformly from range [0,max_log] and then
|
||||
// return "base" random bits. The effect is to pick a number in the
|
||||
// range [0,2^max_log-1] with exponential bias towards smaller numbers.
|
||||
uint32_t Skewed(int max_log) { return Uniform(1 << Uniform(max_log + 1)); }
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_RANDOM_H_
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/status.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "port/port.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
const char* Status::CopyState(const char* state) {
|
||||
uint32_t size;
|
||||
std::memcpy(&size, state, sizeof(size));
|
||||
char* result = new char[size + 5];
|
||||
std::memcpy(result, state, size + 5);
|
||||
return result;
|
||||
}
|
||||
|
||||
Status::Status(Code code, const Slice& msg, const Slice& msg2) {
|
||||
assert(code != kOk);
|
||||
const uint32_t len1 = static_cast<uint32_t>(msg.size());
|
||||
const uint32_t len2 = static_cast<uint32_t>(msg2.size());
|
||||
const uint32_t size = len1 + (len2 ? (2 + len2) : 0);
|
||||
char* result = new char[size + 5];
|
||||
std::memcpy(result, &size, sizeof(size));
|
||||
result[4] = static_cast<char>(code);
|
||||
std::memcpy(result + 5, msg.data(), len1);
|
||||
if (len2) {
|
||||
result[5 + len1] = ':';
|
||||
result[6 + len1] = ' ';
|
||||
std::memcpy(result + 7 + len1, msg2.data(), len2);
|
||||
}
|
||||
state_ = result;
|
||||
}
|
||||
|
||||
std::string Status::ToString() const {
|
||||
if (state_ == nullptr) {
|
||||
return "OK";
|
||||
} else {
|
||||
char tmp[30];
|
||||
const char* type;
|
||||
switch (code()) {
|
||||
case kOk:
|
||||
type = "OK";
|
||||
break;
|
||||
case kNotFound:
|
||||
type = "NotFound: ";
|
||||
break;
|
||||
case kCorruption:
|
||||
type = "Corruption: ";
|
||||
break;
|
||||
case kNotSupported:
|
||||
type = "Not implemented: ";
|
||||
break;
|
||||
case kInvalidArgument:
|
||||
type = "Invalid argument: ";
|
||||
break;
|
||||
case kIOError:
|
||||
type = "IO error: ";
|
||||
break;
|
||||
default:
|
||||
std::snprintf(tmp, sizeof(tmp),
|
||||
"Unknown code(%d): ", static_cast<int>(code()));
|
||||
type = tmp;
|
||||
break;
|
||||
}
|
||||
std::string result(type);
|
||||
uint32_t length;
|
||||
std::memcpy(&length, state_, sizeof(length));
|
||||
result.append(state_ + 5, length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "leveldb/status.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "leveldb/slice.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
TEST(Status, MoveConstructor) {
|
||||
{
|
||||
Status ok = Status::OK();
|
||||
Status ok2 = std::move(ok);
|
||||
|
||||
ASSERT_TRUE(ok2.ok());
|
||||
}
|
||||
|
||||
{
|
||||
Status status = Status::NotFound("custom NotFound status message");
|
||||
Status status2 = std::move(status);
|
||||
|
||||
ASSERT_TRUE(status2.IsNotFound());
|
||||
ASSERT_EQ("NotFound: custom NotFound status message", status2.ToString());
|
||||
}
|
||||
|
||||
{
|
||||
Status self_moved = Status::IOError("custom IOError status message");
|
||||
|
||||
// Needed to bypass compiler warning about explicit move-assignment.
|
||||
Status& self_moved_reference = self_moved;
|
||||
self_moved_reference = std::move(self_moved);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "util/testutil.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "util/random.h"
|
||||
|
||||
namespace leveldb {
|
||||
namespace test {
|
||||
|
||||
Slice RandomString(Random* rnd, int len, std::string* dst) {
|
||||
dst->resize(len);
|
||||
for (int i = 0; i < len; i++) {
|
||||
(*dst)[i] = static_cast<char>(' ' + rnd->Uniform(95)); // ' ' .. '~'
|
||||
}
|
||||
return Slice(*dst);
|
||||
}
|
||||
|
||||
std::string RandomKey(Random* rnd, int len) {
|
||||
// Make sure to generate a wide variety of characters so we
|
||||
// test the boundary conditions for short-key optimizations.
|
||||
static const char kTestChars[] = {'\0', '\1', 'a', 'b', 'c',
|
||||
'd', 'e', '\xfd', '\xfe', '\xff'};
|
||||
std::string result;
|
||||
for (int i = 0; i < len; i++) {
|
||||
result += kTestChars[rnd->Uniform(sizeof(kTestChars))];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Slice CompressibleString(Random* rnd, double compressed_fraction, size_t len,
|
||||
std::string* dst) {
|
||||
int raw = static_cast<int>(len * compressed_fraction);
|
||||
if (raw < 1) raw = 1;
|
||||
std::string raw_data;
|
||||
RandomString(rnd, raw, &raw_data);
|
||||
|
||||
// Duplicate the random data until we have filled "len" bytes
|
||||
dst->clear();
|
||||
while (dst->size() < len) {
|
||||
dst->append(raw_data);
|
||||
}
|
||||
dst->resize(len);
|
||||
return Slice(*dst);
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace leveldb
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_TESTUTIL_H_
|
||||
#define STORAGE_LEVELDB_UTIL_TESTUTIL_H_
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "helpers/memenv/memenv.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace leveldb {
|
||||
namespace test {
|
||||
|
||||
MATCHER(IsOK, "") { return arg.ok(); }
|
||||
|
||||
// Macros for testing the results of functions that return leveldb::Status or
|
||||
// absl::StatusOr<T> (for any type T).
|
||||
#define EXPECT_LEVELDB_OK(expression) \
|
||||
EXPECT_THAT(expression, leveldb::test::IsOK())
|
||||
#define ASSERT_LEVELDB_OK(expression) \
|
||||
ASSERT_THAT(expression, leveldb::test::IsOK())
|
||||
|
||||
// Returns the random seed used at the start of the current test run.
|
||||
inline int RandomSeed() {
|
||||
return testing::UnitTest::GetInstance()->random_seed();
|
||||
}
|
||||
|
||||
// Store in *dst a random string of length "len" and return a Slice that
|
||||
// references the generated data.
|
||||
Slice RandomString(Random* rnd, int len, std::string* dst);
|
||||
|
||||
// Return a random key with the specified length that may contain interesting
|
||||
// characters (e.g. \x00, \xff, etc.).
|
||||
std::string RandomKey(Random* rnd, int len);
|
||||
|
||||
// Store in *dst a string of length "len" that will compress to
|
||||
// "N*compressed_fraction" bytes and return a Slice that references
|
||||
// the generated data.
|
||||
Slice CompressibleString(Random* rnd, double compressed_fraction, size_t len,
|
||||
std::string* dst);
|
||||
|
||||
// A wrapper that allows injection of errors.
|
||||
class ErrorEnv : public EnvWrapper {
|
||||
public:
|
||||
bool writable_file_error_;
|
||||
int num_writable_file_errors_;
|
||||
|
||||
ErrorEnv()
|
||||
: EnvWrapper(NewMemEnv(Env::Default())),
|
||||
writable_file_error_(false),
|
||||
num_writable_file_errors_(0) {}
|
||||
~ErrorEnv() override { delete target(); }
|
||||
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
WritableFile** result) override {
|
||||
if (writable_file_error_) {
|
||||
++num_writable_file_errors_;
|
||||
*result = nullptr;
|
||||
return Status::IOError(fname, "fake error");
|
||||
}
|
||||
return target()->NewWritableFile(fname, result);
|
||||
}
|
||||
|
||||
Status NewAppendableFile(const std::string& fname,
|
||||
WritableFile** result) override {
|
||||
if (writable_file_error_) {
|
||||
++num_writable_file_errors_;
|
||||
*result = nullptr;
|
||||
return Status::IOError(fname, "fake error");
|
||||
}
|
||||
return target()->NewAppendableFile(fname, result);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_TESTUTIL_H_
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2018 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
// Logger implementation for the Windows platform.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_UTIL_WINDOWS_LOGGER_H_
|
||||
#define STORAGE_LEVELDB_UTIL_WINDOWS_LOGGER_H_
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
|
||||
#include "leveldb/env.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class WindowsLogger final : public Logger {
|
||||
public:
|
||||
// Creates a logger that writes to the given file.
|
||||
//
|
||||
// The PosixLogger instance takes ownership of the file handle.
|
||||
explicit WindowsLogger(std::FILE* fp) : fp_(fp) { assert(fp != nullptr); }
|
||||
|
||||
~WindowsLogger() override { std::fclose(fp_); }
|
||||
|
||||
void Logv(const char* format, std::va_list arguments) override {
|
||||
// Record the time as close to the Logv() call as possible.
|
||||
SYSTEMTIME now_components;
|
||||
::GetLocalTime(&now_components);
|
||||
|
||||
// Record the thread ID.
|
||||
constexpr const int kMaxThreadIdSize = 32;
|
||||
std::ostringstream thread_stream;
|
||||
thread_stream << std::this_thread::get_id();
|
||||
std::string thread_id = thread_stream.str();
|
||||
if (thread_id.size() > kMaxThreadIdSize) {
|
||||
thread_id.resize(kMaxThreadIdSize);
|
||||
}
|
||||
|
||||
// We first attempt to print into a stack-allocated buffer. If this attempt
|
||||
// fails, we make a second attempt with a dynamically allocated buffer.
|
||||
constexpr const int kStackBufferSize = 512;
|
||||
char stack_buffer[kStackBufferSize];
|
||||
static_assert(sizeof(stack_buffer) == static_cast<size_t>(kStackBufferSize),
|
||||
"sizeof(char) is expected to be 1 in C++");
|
||||
|
||||
int dynamic_buffer_size = 0; // Computed in the first iteration.
|
||||
for (int iteration = 0; iteration < 2; ++iteration) {
|
||||
const int buffer_size =
|
||||
(iteration == 0) ? kStackBufferSize : dynamic_buffer_size;
|
||||
char* const buffer =
|
||||
(iteration == 0) ? stack_buffer : new char[dynamic_buffer_size];
|
||||
|
||||
// Print the header into the buffer.
|
||||
int buffer_offset = std::snprintf(
|
||||
buffer, buffer_size, "%04d/%02d/%02d-%02d:%02d:%02d.%06d %s ",
|
||||
now_components.wYear, now_components.wMonth, now_components.wDay,
|
||||
now_components.wHour, now_components.wMinute, now_components.wSecond,
|
||||
static_cast<int>(now_components.wMilliseconds * 1000),
|
||||
thread_id.c_str());
|
||||
|
||||
// The header can be at most 28 characters (10 date + 15 time +
|
||||
// 3 delimiters) plus the thread ID, which should fit comfortably into the
|
||||
// static buffer.
|
||||
assert(buffer_offset <= 28 + kMaxThreadIdSize);
|
||||
static_assert(28 + kMaxThreadIdSize < kStackBufferSize,
|
||||
"stack-allocated buffer may not fit the message header");
|
||||
assert(buffer_offset < buffer_size);
|
||||
|
||||
// Print the message into the buffer.
|
||||
std::va_list arguments_copy;
|
||||
va_copy(arguments_copy, arguments);
|
||||
buffer_offset +=
|
||||
std::vsnprintf(buffer + buffer_offset, buffer_size - buffer_offset,
|
||||
format, arguments_copy);
|
||||
va_end(arguments_copy);
|
||||
|
||||
// The code below may append a newline at the end of the buffer, which
|
||||
// requires an extra character.
|
||||
if (buffer_offset >= buffer_size - 1) {
|
||||
// The message did not fit into the buffer.
|
||||
if (iteration == 0) {
|
||||
// Re-run the loop and use a dynamically-allocated buffer. The buffer
|
||||
// will be large enough for the log message, an extra newline and a
|
||||
// null terminator.
|
||||
dynamic_buffer_size = buffer_offset + 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The dynamically-allocated buffer was incorrectly sized. This should
|
||||
// not happen, assuming a correct implementation of std::(v)snprintf.
|
||||
// Fail in tests, recover by truncating the log message in production.
|
||||
assert(false);
|
||||
buffer_offset = buffer_size - 1;
|
||||
}
|
||||
|
||||
// Add a newline if necessary.
|
||||
if (buffer[buffer_offset - 1] != '\n') {
|
||||
buffer[buffer_offset] = '\n';
|
||||
++buffer_offset;
|
||||
}
|
||||
|
||||
assert(buffer_offset <= buffer_size);
|
||||
std::fwrite(buffer, 1, buffer_offset, fp_);
|
||||
std::fflush(fp_);
|
||||
|
||||
if (iteration != 0) {
|
||||
delete[] buffer;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::FILE* const fp_;
|
||||
};
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_UTIL_WINDOWS_LOGGER_H_
|
||||
Reference in New Issue
Block a user