mirror of
https://github.com/google/leveldb.git
synced 2026-08-20 10:43:29 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e353fbc7ea | ||
|
|
269fc6ca94 | ||
|
|
0cfb990d58 | ||
|
|
0b9a89f40e | ||
|
|
748539c183 | ||
|
|
5bd76dc10d | ||
|
|
7b094f12e4 | ||
|
|
28dad918f2 | ||
|
|
514c943a8e | ||
|
|
d84c825a70 | ||
|
|
ea2e9195fc | ||
|
|
40768657bc | ||
|
|
946e5b5a4c | ||
|
|
dd0d562b4d | ||
|
|
b914f91b10 | ||
|
|
075a35a6d3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ build_config.mk
|
||||
*.so.*
|
||||
*_test
|
||||
db_bench
|
||||
leveldbutil
|
||||
|
||||
4
AUTHORS
4
AUTHORS
@@ -6,3 +6,7 @@ Google Inc.
|
||||
# Initial version authors:
|
||||
Jeffrey Dean <jeff@google.com>
|
||||
Sanjay Ghemawat <sanjay@google.com>
|
||||
|
||||
# Partial list of contributors:
|
||||
Kevin Regan <kevin.d.regan@gmail.com>
|
||||
Johan Bilien <jobi@litl.com>
|
||||
|
||||
106
Makefile
106
Makefile
@@ -2,11 +2,6 @@
|
||||
# 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.
|
||||
|
||||
# Inherit some settings from environment variables, if available
|
||||
CXX ?= g++
|
||||
CC ?= gcc
|
||||
INSTALL_PATH ?= $(CURDIR)
|
||||
|
||||
#-----------------------------------------------
|
||||
# Uncomment exactly one of the lines labelled (A), (B), and (C) below
|
||||
# to switch between compilation modes.
|
||||
@@ -17,7 +12,8 @@ OPT ?= -O2 -DNDEBUG # (A) Production use (optimized mode)
|
||||
#-----------------------------------------------
|
||||
|
||||
# detect what platform we're building on
|
||||
$(shell ./build_detect_platform build_config.mk)
|
||||
$(shell CC="$(CC)" CXX="$(CXX)" TARGET_OS="$(TARGET_OS)" \
|
||||
./build_detect_platform build_config.mk ./)
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include build_config.mk
|
||||
|
||||
@@ -25,6 +21,7 @@ CFLAGS += -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT)
|
||||
|
||||
LDFLAGS += $(PLATFORM_LDFLAGS)
|
||||
LIBS += $(PLATFORM_LIBS)
|
||||
|
||||
LIBOBJECTS = $(SOURCES:.cc=.o)
|
||||
MEMENVOBJECTS = $(MEMENV_SOURCES:.cc=.o)
|
||||
@@ -34,6 +31,7 @@ TESTHARNESS = ./util/testharness.o $(TESTUTIL)
|
||||
|
||||
TESTS = \
|
||||
arena_test \
|
||||
autocompact_test \
|
||||
bloom_test \
|
||||
c_test \
|
||||
cache_test \
|
||||
@@ -45,6 +43,8 @@ TESTS = \
|
||||
env_test \
|
||||
filename_test \
|
||||
filter_block_test \
|
||||
issue178_test \
|
||||
issue200_test \
|
||||
log_test \
|
||||
memenv_test \
|
||||
skiplist_test \
|
||||
@@ -53,7 +53,7 @@ TESTS = \
|
||||
version_set_test \
|
||||
write_batch_test
|
||||
|
||||
PROGRAMS = db_bench $(TESTS)
|
||||
PROGRAMS = db_bench leveldbutil $(TESTS)
|
||||
BENCHMARKS = db_bench_sqlite3 db_bench_tree_db
|
||||
|
||||
LIBRARY = libleveldb.a
|
||||
@@ -63,21 +63,31 @@ default: all
|
||||
|
||||
# Should we build shared libraries?
|
||||
ifneq ($(PLATFORM_SHARED_EXT),)
|
||||
|
||||
ifneq ($(PLATFORM_SHARED_VERSIONED),true)
|
||||
SHARED1 = libleveldb.$(PLATFORM_SHARED_EXT)
|
||||
SHARED2 = $(SHARED1)
|
||||
SHARED3 = $(SHARED1)
|
||||
SHARED = $(SHARED1)
|
||||
else
|
||||
# Update db.h if you change these.
|
||||
SHARED_MAJOR = 1
|
||||
SHARED_MINOR = 4
|
||||
SHARED_MINOR = 17
|
||||
SHARED1 = libleveldb.$(PLATFORM_SHARED_EXT)
|
||||
SHARED2 = $(SHARED1).$(SHARED_MAJOR)
|
||||
SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
SHARED = $(SHARED1) $(SHARED2) $(SHARED3)
|
||||
$(SHARED3):
|
||||
$(CXX) $(LDFLAGS) $(PLATFORM_SHARED_LDFLAGS)$(INSTALL_PATH)/$(SHARED2) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(SOURCES) -o $(SHARED3)
|
||||
$(SHARED2): $(SHARED3)
|
||||
ln -fs $(SHARED3) $(SHARED2)
|
||||
$(SHARED1): $(SHARED3)
|
||||
ln -fs $(SHARED3) $(SHARED1)
|
||||
$(SHARED2): $(SHARED3)
|
||||
ln -fs $(SHARED3) $(SHARED2)
|
||||
endif
|
||||
|
||||
$(SHARED3):
|
||||
$(CXX) $(LDFLAGS) $(PLATFORM_SHARED_LDFLAGS)$(SHARED2) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(SOURCES) -o $(SHARED3) $(LIBS)
|
||||
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
|
||||
all: $(SHARED) $(LIBRARY)
|
||||
|
||||
check: all $(PROGRAMS) $(TESTS)
|
||||
@@ -92,94 +102,108 @@ $(LIBRARY): $(LIBOBJECTS)
|
||||
$(AR) -rs $@ $(LIBOBJECTS)
|
||||
|
||||
db_bench: db/db_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) db/db_bench.o $(LIBOBJECTS) $(TESTUTIL) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/db_bench.o $(LIBOBJECTS) $(TESTUTIL) -o $@ $(LIBS)
|
||||
|
||||
db_bench_sqlite3: doc/bench/db_bench_sqlite3.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) doc/bench/db_bench_sqlite3.o $(LIBOBJECTS) $(TESTUTIL) -o $@ $(LDFLAGS) -lsqlite3
|
||||
$(CXX) $(LDFLAGS) doc/bench/db_bench_sqlite3.o $(LIBOBJECTS) $(TESTUTIL) -o $@ -lsqlite3 $(LIBS)
|
||||
|
||||
db_bench_tree_db: doc/bench/db_bench_tree_db.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(CXX) doc/bench/db_bench_tree_db.o $(LIBOBJECTS) $(TESTUTIL) -o $@ $(LDFLAGS) -lkyotocabinet
|
||||
$(CXX) $(LDFLAGS) doc/bench/db_bench_tree_db.o $(LIBOBJECTS) $(TESTUTIL) -o $@ -lkyotocabinet $(LIBS)
|
||||
|
||||
leveldbutil: db/leveldb_main.o $(LIBOBJECTS)
|
||||
$(CXX) $(LDFLAGS) db/leveldb_main.o $(LIBOBJECTS) -o $@ $(LIBS)
|
||||
|
||||
arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
autocompact_test: db/autocompact_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) $(LDFLAGS) db/autocompact_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
bloom_test: util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
c_test: db/c_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/c_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/c_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
cache_test: util/cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/cache_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) util/cache_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
coding_test: util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
corruption_test: db/corruption_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/corruption_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/corruption_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
crc32c_test: util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) util/crc32c_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
db_test: db/db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/db_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/db_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
dbformat_test: db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
env_test: util/env_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) util/env_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) util/env_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
filename_test: db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
filter_block_test: table/filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) table/filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) table/filter_block_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
issue178_test: issues/issue178_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) $(LDFLAGS) issues/issue178_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
issue200_test: issues/issue200_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) $(LDFLAGS) issues/issue200_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
log_test: db/log_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/log_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/log_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
table_test: table/table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) table/table_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) table/table_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
skiplist_test: db/skiplist_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/skiplist_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/skiplist_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
version_edit_test: db/version_edit_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/version_edit_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/version_edit_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
version_set_test: db/version_set_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/version_set_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/version_set_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
write_batch_test: db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(CXX) db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) db/write_batch_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
$(MEMENVLIBRARY) : $(MEMENVOBJECTS)
|
||||
rm -f $@
|
||||
$(AR) -rs $@ $(MEMENVOBJECTS)
|
||||
|
||||
memenv_test : helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS)
|
||||
$(CXX) helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS) -o $@ $(LDFLAGS)
|
||||
$(CXX) $(LDFLAGS) helpers/memenv/memenv_test.o $(MEMENVLIBRARY) $(LIBRARY) $(TESTHARNESS) -o $@ $(LIBS)
|
||||
|
||||
ifeq ($(PLATFORM), IOS)
|
||||
# For iOS, create universal object files to be used on both the simulator and
|
||||
# a device.
|
||||
SIMULATORROOT=/Developer/Platforms/iPhoneSimulator.platform/Developer
|
||||
DEVICEROOT=/Developer/Platforms/iPhoneOS.platform/Developer
|
||||
IOSVERSION=$(shell defaults read /Developer/Platforms/iPhoneOS.platform/version CFBundleShortVersionString)
|
||||
PLATFORMSROOT=/Applications/Xcode.app/Contents/Developer/Platforms
|
||||
SIMULATORROOT=$(PLATFORMSROOT)/iPhoneSimulator.platform/Developer
|
||||
DEVICEROOT=$(PLATFORMSROOT)/iPhoneOS.platform/Developer
|
||||
IOSVERSION=$(shell defaults read $(PLATFORMSROOT)/iPhoneOS.platform/version CFBundleShortVersionString)
|
||||
IOSARCH=-arch armv6 -arch armv7 -arch armv7s -arch arm64
|
||||
|
||||
.cc.o:
|
||||
mkdir -p ios-x86/$(dir $@)
|
||||
$(SIMULATORROOT)/usr/bin/$(CXX) $(CXXFLAGS) -isysroot $(SIMULATORROOT)/SDKs/iPhoneSimulator$(IOSVERSION).sdk -arch i686 -c $< -o ios-x86/$@
|
||||
$(CXX) $(CXXFLAGS) -isysroot $(SIMULATORROOT)/SDKs/iPhoneSimulator$(IOSVERSION).sdk -arch i686 -arch x86_64 -c $< -o ios-x86/$@
|
||||
mkdir -p ios-arm/$(dir $@)
|
||||
$(DEVICEROOT)/usr/bin/$(CXX) $(CXXFLAGS) -isysroot $(DEVICEROOT)/SDKs/iPhoneOS$(IOSVERSION).sdk -arch armv6 -arch armv7 -c $< -o ios-arm/$@
|
||||
xcrun -sdk iphoneos $(CXX) $(CXXFLAGS) -isysroot $(DEVICEROOT)/SDKs/iPhoneOS$(IOSVERSION).sdk $(IOSARCH) -c $< -o ios-arm/$@
|
||||
lipo ios-x86/$@ ios-arm/$@ -create -output $@
|
||||
|
||||
.c.o:
|
||||
mkdir -p ios-x86/$(dir $@)
|
||||
$(SIMULATORROOT)/usr/bin/$(CC) $(CFLAGS) -isysroot $(SIMULATORROOT)/SDKs/iPhoneSimulator$(IOSVERSION).sdk -arch i686 -c $< -o ios-x86/$@
|
||||
$(CC) $(CFLAGS) -isysroot $(SIMULATORROOT)/SDKs/iPhoneSimulator$(IOSVERSION).sdk -arch i686 -arch x86_64 -c $< -o ios-x86/$@
|
||||
mkdir -p ios-arm/$(dir $@)
|
||||
$(DEVICEROOT)/usr/bin/$(CC) $(CFLAGS) -isysroot $(DEVICEROOT)/SDKs/iPhoneOS$(IOSVERSION).sdk -arch armv6 -arch armv7 -c $< -o ios-arm/$@
|
||||
xcrun -sdk iphoneos $(CC) $(CFLAGS) -isysroot $(DEVICEROOT)/SDKs/iPhoneOS$(IOSVERSION).sdk $(IOSARCH) -c $< -o ios-arm/$@
|
||||
lipo ios-x86/$@ ios-arm/$@ -create -output $@
|
||||
|
||||
else
|
||||
|
||||
1
TODO
1
TODO
@@ -7,6 +7,7 @@ db
|
||||
within [start_key..end_key]? For Chrome, deletion of obsolete
|
||||
object stores, etc. can be done in the background anyway, so
|
||||
probably not that important.
|
||||
- There have been requests for MultiGet.
|
||||
|
||||
After a range is completely deleted, what gets rid of the
|
||||
corresponding files if we do no future changes to that range. Make
|
||||
|
||||
@@ -4,18 +4,31 @@
|
||||
# argument, which in turn gets read while processing Makefile.
|
||||
#
|
||||
# The output will set the following variables:
|
||||
# CC C Compiler path
|
||||
# CXX C++ Compiler path
|
||||
# PLATFORM_LDFLAGS Linker flags
|
||||
# PLATFORM_LIBS Libraries flags
|
||||
# PLATFORM_SHARED_EXT Extension for shared libraries
|
||||
# PLATFORM_SHARED_LDFLAGS Flags for building shared library
|
||||
# This flag is embedded just before the name
|
||||
# of the shared library without intervening spaces
|
||||
# PLATFORM_SHARED_CFLAGS Flags for compiling objects for shared library
|
||||
# PLATFORM_CCFLAGS C compiler flags
|
||||
# PLATFORM_CXXFLAGS C++ compiler flags. Will contain:
|
||||
# -DLEVELDB_PLATFORM_POSIX if cstdatomic is present
|
||||
# -DLEVELDB_PLATFORM_NOATOMIC if it is not
|
||||
# PLATFORM_SHARED_VERSIONED Set to 'true' if platform supports versioned
|
||||
# shared libraries, empty otherwise.
|
||||
#
|
||||
# The PLATFORM_CCFLAGS and PLATFORM_CXXFLAGS might include the following:
|
||||
#
|
||||
# -DLEVELDB_CSTDATOMIC_PRESENT if <cstdatomic> is present
|
||||
# -DLEVELDB_PLATFORM_POSIX for Posix-based platforms
|
||||
# -DSNAPPY if the Snappy library is present
|
||||
#
|
||||
|
||||
OUTPUT=$1
|
||||
if test -z "$OUTPUT"; then
|
||||
echo "usage: $0 <output-filename>"
|
||||
PREFIX=$2
|
||||
if test -z "$OUTPUT" || test -z "$PREFIX"; then
|
||||
echo "usage: $0 <output-filename> <directory_prefix>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -23,76 +36,113 @@ fi
|
||||
rm -f $OUTPUT
|
||||
touch $OUTPUT
|
||||
|
||||
if test -z "$CC"; then
|
||||
CC=cc
|
||||
fi
|
||||
|
||||
if test -z "$CXX"; then
|
||||
CXX=g++
|
||||
fi
|
||||
|
||||
if test -z "$TMPDIR"; then
|
||||
TMPDIR=/tmp
|
||||
fi
|
||||
|
||||
# Detect OS
|
||||
if test -z "$TARGET_OS"; then
|
||||
TARGET_OS=`uname -s`
|
||||
fi
|
||||
|
||||
COMMON_FLAGS=
|
||||
CROSS_COMPILE=
|
||||
PLATFORM_CCFLAGS=
|
||||
PLATFORM_CXXFLAGS=
|
||||
PLATFORM_LDFLAGS=
|
||||
PLATFORM_LIBS=
|
||||
PLATFORM_SHARED_EXT="so"
|
||||
PLATFORM_SHARED_LDFLAGS="-shared -Wl,-soname -Wl,"
|
||||
PLATFORM_SHARED_CFLAGS="-fPIC"
|
||||
PLATFORM_SHARED_VERSIONED=true
|
||||
|
||||
MEMCMP_FLAG=
|
||||
if [ "$CXX" = "g++" ]; then
|
||||
# Use libc's memcmp instead of GCC's memcmp. This results in ~40%
|
||||
# performance improvement on readrandom under gcc 4.4.3 on Linux/x86.
|
||||
MEMCMP_FLAG="-fno-builtin-memcmp"
|
||||
fi
|
||||
|
||||
# On GCC, we pick libc's memcmp over GCC's memcmp via -fno-builtin-memcmp
|
||||
case "$TARGET_OS" in
|
||||
Darwin)
|
||||
PLATFORM=OS_MACOSX
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -DOS_MACOSX"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -DOS_MACOSX"
|
||||
PLATFORM_SHARED_EXT=dylib
|
||||
PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name "
|
||||
[ -z "$INSTALL_PATH" ] && INSTALL_PATH=`pwd`
|
||||
PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name $INSTALL_PATH/"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
Linux)
|
||||
PLATFORM=OS_LINUX
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -pthread -DOS_LINUX"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -pthread -DOS_LINUX"
|
||||
PLATFORM_LDFLAGS="-pthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
SunOS)
|
||||
PLATFORM=OS_SOLARIS
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -D_REENTRANT -DOS_SOLARIS"
|
||||
PLATFORM_LDFLAGS="-lpthread -lrt"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_SOLARIS"
|
||||
PLATFORM_LIBS="-lpthread -lrt"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
FreeBSD)
|
||||
PLATFORM=OS_FREEBSD
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -D_REENTRANT -DOS_FREEBSD"
|
||||
PLATFORM_LDFLAGS="-lpthread"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_FREEBSD"
|
||||
PLATFORM_LIBS="-lpthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
NetBSD)
|
||||
PLATFORM=OS_NETBSD
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -D_REENTRANT -DOS_NETBSD"
|
||||
PLATFORM_LDFLAGS="-lpthread -lgcc_s"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_NETBSD"
|
||||
PLATFORM_LIBS="-lpthread -lgcc_s"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
OpenBSD)
|
||||
PLATFORM=OS_OPENBSD
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -D_REENTRANT -DOS_OPENBSD"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_OPENBSD"
|
||||
PLATFORM_LDFLAGS="-pthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
DragonFly)
|
||||
PLATFORM=OS_DRAGONFLYBSD
|
||||
COMMON_FLAGS="-fno-builtin-memcmp -D_REENTRANT -DOS_DRAGONFLYBSD"
|
||||
PLATFORM_LDFLAGS="-lpthread"
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_DRAGONFLYBSD"
|
||||
PLATFORM_LIBS="-lpthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
;;
|
||||
OS_ANDROID_CROSSCOMPILE)
|
||||
PLATFORM="$TARGET_OS"
|
||||
COMMON_FLAGS=""
|
||||
PLATFORM_LDFLAGS=""
|
||||
PORT_FILE=port/port_android.cc
|
||||
PLATFORM=OS_ANDROID
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX"
|
||||
PLATFORM_LDFLAGS="" # All pthread features are in the Android C library
|
||||
PORT_FILE=port/port_posix.cc
|
||||
CROSS_COMPILE=true
|
||||
;;
|
||||
HP-UX)
|
||||
PLATFORM=OS_HPUX
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -D_REENTRANT -DOS_HPUX"
|
||||
PLATFORM_LDFLAGS="-pthread"
|
||||
PORT_FILE=port/port_posix.cc
|
||||
# man ld: +h internal_name
|
||||
PLATFORM_SHARED_LDFLAGS="-shared -Wl,+h -Wl,"
|
||||
;;
|
||||
IOS)
|
||||
PLATFORM=IOS
|
||||
COMMON_FLAGS="$MEMCMP_FLAG -DOS_MACOSX"
|
||||
[ -z "$INSTALL_PATH" ] && INSTALL_PATH=`pwd`
|
||||
PORT_FILE=port/port_posix.cc
|
||||
PLATFORM_SHARED_EXT=
|
||||
PLATFORM_SHARED_LDFLAGS=
|
||||
PLATFORM_SHARED_CFLAGS=
|
||||
PLATFORM_SHARED_VERSIONED=
|
||||
;;
|
||||
*)
|
||||
echo "Unknown platform!"
|
||||
echo "Unknown platform!" >&2
|
||||
exit 1
|
||||
esac
|
||||
|
||||
@@ -100,11 +150,14 @@ esac
|
||||
# except for the test and benchmark files. By default, find will output a list
|
||||
# of all files matching either rule, so we need to append -print to make the
|
||||
# prune take effect.
|
||||
DIRS="util db table"
|
||||
DIRS="$PREFIX/db $PREFIX/util $PREFIX/table"
|
||||
|
||||
set -f # temporarily disable globbing so that our patterns aren't expanded
|
||||
PRUNE_TEST="-name *test*.cc -prune"
|
||||
PRUNE_BENCH="-name *_bench.cc -prune"
|
||||
PORTABLE_FILES=`find $DIRS $PRUNE_TEST -o $PRUNE_BENCH -o -name '*.cc' -print | sort | tr "\n" " "`
|
||||
PRUNE_TOOL="-name leveldb_main.cc -prune"
|
||||
PORTABLE_FILES=`find $DIRS $PRUNE_TEST -o $PRUNE_BENCH -o $PRUNE_TOOL -o -name '*.cc' -print | sort | sed "s,^$PREFIX/,," | tr "\n" " "`
|
||||
|
||||
set +f # re-enable globbing
|
||||
|
||||
# The sources consist of the portable files, plus the platform-specific port
|
||||
@@ -112,12 +165,14 @@ set +f # re-enable globbing
|
||||
echo "SOURCES=$PORTABLE_FILES $PORT_FILE" >> $OUTPUT
|
||||
echo "MEMENV_SOURCES=helpers/memenv/memenv.cc" >> $OUTPUT
|
||||
|
||||
if [ "$PLATFORM" = "OS_ANDROID_CROSSCOMPILE" ]; then
|
||||
if [ "$CROSS_COMPILE" = "true" ]; then
|
||||
# Cross-compiling; do not try any compilation tests.
|
||||
true
|
||||
else
|
||||
CXXOUTPUT="${TMPDIR}/leveldb_build_detect_platform-cxx.$$"
|
||||
|
||||
# If -std=c++0x works, use <cstdatomic>. Otherwise use port_posix.h.
|
||||
$CXX $CFLAGS -std=c++0x -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $CXXFLAGS -std=c++0x -x c++ - -o $CXXOUTPUT 2>/dev/null <<EOF
|
||||
#include <cstdatomic>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -130,31 +185,37 @@ EOF
|
||||
|
||||
# Test whether Snappy library is installed
|
||||
# http://code.google.com/p/snappy/
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $CXXFLAGS -x c++ - -o $CXXOUTPUT 2>/dev/null <<EOF
|
||||
#include <snappy.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy"
|
||||
PLATFORM_LIBS="$PLATFORM_LIBS -lsnappy"
|
||||
fi
|
||||
|
||||
# Test whether tcmalloc is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -ltcmalloc 2>/dev/null <<EOF
|
||||
$CXX $CXXFLAGS -x c++ - -o $CXXOUTPUT -ltcmalloc 2>/dev/null <<EOF
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
PLATFORM_LIBS="$PLATFORM_LIBS -ltcmalloc"
|
||||
fi
|
||||
|
||||
rm -f $CXXOUTPUT 2>/dev/null
|
||||
fi
|
||||
|
||||
PLATFORM_CCFLAGS="$PLATFORM_CCFLAGS $COMMON_FLAGS"
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS $COMMON_FLAGS"
|
||||
|
||||
echo "CC=$CC" >> $OUTPUT
|
||||
echo "CXX=$CXX" >> $OUTPUT
|
||||
echo "PLATFORM=$PLATFORM" >> $OUTPUT
|
||||
echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> $OUTPUT
|
||||
echo "PLATFORM_LIBS=$PLATFORM_LIBS" >> $OUTPUT
|
||||
echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> $OUTPUT
|
||||
echo "PLATFORM_CXXFLAGS=$PLATFORM_CXXFLAGS" >> $OUTPUT
|
||||
echo "PLATFORM_SHARED_CFLAGS=$PLATFORM_SHARED_CFLAGS" >> $OUTPUT
|
||||
echo "PLATFORM_SHARED_EXT=$PLATFORM_SHARED_EXT" >> $OUTPUT
|
||||
echo "PLATFORM_SHARED_LDFLAGS=$PLATFORM_SHARED_LDFLAGS" >> $OUTPUT
|
||||
echo "PLATFORM_SHARED_VERSIONED=$PLATFORM_SHARED_VERSIONED" >> $OUTPUT
|
||||
|
||||
118
db/autocompact_test.cc
Normal file
118
db/autocompact_test.cc
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2013 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/db.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "leveldb/cache.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class AutoCompactTest {
|
||||
public:
|
||||
std::string dbname_;
|
||||
Cache* tiny_cache_;
|
||||
Options options_;
|
||||
DB* db_;
|
||||
|
||||
AutoCompactTest() {
|
||||
dbname_ = test::TmpDir() + "/autocompact_test";
|
||||
tiny_cache_ = NewLRUCache(100);
|
||||
options_.block_cache = tiny_cache_;
|
||||
DestroyDB(dbname_, options_);
|
||||
options_.create_if_missing = true;
|
||||
options_.compression = kNoCompression;
|
||||
ASSERT_OK(DB::Open(options_, dbname_, &db_));
|
||||
}
|
||||
|
||||
~AutoCompactTest() {
|
||||
delete db_;
|
||||
DestroyDB(dbname_, Options());
|
||||
delete tiny_cache_;
|
||||
}
|
||||
|
||||
std::string Key(int i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "key%06d", i);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
uint64_t Size(const Slice& start, const Slice& limit) {
|
||||
Range r(start, limit);
|
||||
uint64_t size;
|
||||
db_->GetApproximateSizes(&r, 1, &size);
|
||||
return size;
|
||||
}
|
||||
|
||||
void DoReads(int n);
|
||||
};
|
||||
|
||||
static const int kValueSize = 200 * 1024;
|
||||
static const int kTotalSize = 100 * 1024 * 1024;
|
||||
static const int kCount = kTotalSize / kValueSize;
|
||||
|
||||
// Read through the first n keys repeatedly and check that they get
|
||||
// compacted (verified by checking the size of the key space).
|
||||
void AutoCompactTest::DoReads(int n) {
|
||||
std::string value(kValueSize, 'x');
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
|
||||
// Fill database
|
||||
for (int i = 0; i < kCount; i++) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), Key(i), value));
|
||||
}
|
||||
ASSERT_OK(dbi->TEST_CompactMemTable());
|
||||
|
||||
// Delete everything
|
||||
for (int i = 0; i < kCount; i++) {
|
||||
ASSERT_OK(db_->Delete(WriteOptions(), Key(i)));
|
||||
}
|
||||
ASSERT_OK(dbi->TEST_CompactMemTable());
|
||||
|
||||
// Get initial measurement of the space we will be reading.
|
||||
const int64_t initial_size = Size(Key(0), Key(n));
|
||||
const int64_t initial_other_size = Size(Key(n), Key(kCount));
|
||||
|
||||
// Read until size drops significantly.
|
||||
std::string limit_key = Key(n);
|
||||
for (int read = 0; true; read++) {
|
||||
ASSERT_LT(read, 100) << "Taking too long to compact";
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
for (iter->SeekToFirst();
|
||||
iter->Valid() && iter->key().ToString() < limit_key;
|
||||
iter->Next()) {
|
||||
// Drop data
|
||||
}
|
||||
delete iter;
|
||||
// Wait a little bit to allow any triggered compactions to complete.
|
||||
Env::Default()->SleepForMicroseconds(1000000);
|
||||
uint64_t size = Size(Key(0), Key(n));
|
||||
fprintf(stderr, "iter %3d => %7.3f MB [other %7.3f MB]\n",
|
||||
read+1, size/1048576.0, Size(Key(n), Key(kCount))/1048576.0);
|
||||
if (size <= initial_size/10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the size of the key space not touched by the reads
|
||||
// is pretty much unchanged.
|
||||
const int64_t final_other_size = Size(Key(n), Key(kCount));
|
||||
ASSERT_LE(final_other_size, initial_other_size + 1048576);
|
||||
ASSERT_GE(final_other_size, initial_other_size/5 - 1048576);
|
||||
}
|
||||
|
||||
TEST(AutoCompactTest, ReadAll) {
|
||||
DoReads(kCount);
|
||||
}
|
||||
|
||||
TEST(AutoCompactTest, ReadHalf) {
|
||||
DoReads(kCount/2);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return leveldb::test::RunAllTests();
|
||||
}
|
||||
14
db/c.cc
14
db/c.cc
@@ -24,6 +24,8 @@ using leveldb::Env;
|
||||
using leveldb::FileLock;
|
||||
using leveldb::FilterPolicy;
|
||||
using leveldb::Iterator;
|
||||
using leveldb::kMajorVersion;
|
||||
using leveldb::kMinorVersion;
|
||||
using leveldb::Logger;
|
||||
using leveldb::NewBloomFilterPolicy;
|
||||
using leveldb::NewLRUCache;
|
||||
@@ -578,4 +580,16 @@ void leveldb_env_destroy(leveldb_env_t* env) {
|
||||
delete env;
|
||||
}
|
||||
|
||||
void leveldb_free(void* ptr) {
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
int leveldb_major_version() {
|
||||
return kMajorVersion;
|
||||
}
|
||||
|
||||
int leveldb_minor_version() {
|
||||
return kMinorVersion;
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
20
db/c_test.c
20
db/c_test.c
@@ -19,6 +19,13 @@ static void StartPhase(const char* name) {
|
||||
phase = name;
|
||||
}
|
||||
|
||||
static const char* GetTempDir(void) {
|
||||
const char* ret = getenv("TEST_TMPDIR");
|
||||
if (ret == NULL || ret[0] == '\0')
|
||||
ret = "/tmp";
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define CheckNoError(err) \
|
||||
if ((err) != NULL) { \
|
||||
fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, phase, (err)); \
|
||||
@@ -158,7 +165,12 @@ int main(int argc, char** argv) {
|
||||
char* err = NULL;
|
||||
int run = -1;
|
||||
|
||||
snprintf(dbname, sizeof(dbname), "/tmp/leveldb_c_test-%d",
|
||||
CheckCondition(leveldb_major_version() >= 1);
|
||||
CheckCondition(leveldb_minor_version() >= 1);
|
||||
|
||||
snprintf(dbname, sizeof(dbname),
|
||||
"%s/leveldb_c_test-%d",
|
||||
GetTempDir(),
|
||||
((int) geteuid()));
|
||||
|
||||
StartPhase("create_objects");
|
||||
@@ -195,6 +207,12 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(err != NULL);
|
||||
Free(&err);
|
||||
|
||||
StartPhase("leveldb_free");
|
||||
db = leveldb_open(options, dbname, &err);
|
||||
CheckCondition(err != NULL);
|
||||
leveldb_free(err);
|
||||
err = NULL;
|
||||
|
||||
StartPhase("open");
|
||||
leveldb_options_set_create_if_missing(options, 1);
|
||||
db = leveldb_open(options, dbname, &err);
|
||||
|
||||
@@ -35,6 +35,7 @@ class CorruptionTest {
|
||||
CorruptionTest() {
|
||||
tiny_cache_ = NewLRUCache(100);
|
||||
options_.env = &env_;
|
||||
options_.block_cache = tiny_cache_;
|
||||
dbname_ = test::TmpDir() + "/db_test";
|
||||
DestroyDB(dbname_, options_);
|
||||
|
||||
@@ -50,17 +51,14 @@ class CorruptionTest {
|
||||
delete tiny_cache_;
|
||||
}
|
||||
|
||||
Status TryReopen(Options* options = NULL) {
|
||||
Status TryReopen() {
|
||||
delete db_;
|
||||
db_ = NULL;
|
||||
Options opt = (options ? *options : options_);
|
||||
opt.env = &env_;
|
||||
opt.block_cache = tiny_cache_;
|
||||
return DB::Open(opt, dbname_, &db_);
|
||||
return DB::Open(options_, dbname_, &db_);
|
||||
}
|
||||
|
||||
void Reopen(Options* options = NULL) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
void Reopen() {
|
||||
ASSERT_OK(TryReopen());
|
||||
}
|
||||
|
||||
void RepairDB() {
|
||||
@@ -77,7 +75,13 @@ class CorruptionTest {
|
||||
Slice key = Key(i, &key_space);
|
||||
batch.Clear();
|
||||
batch.Put(key, Value(i, &value_space));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
WriteOptions options;
|
||||
// Corrupt() doesn't work without this sync on windows; stat reports 0 for
|
||||
// the file size.
|
||||
if (i == n - 1) {
|
||||
options.sync = true;
|
||||
}
|
||||
ASSERT_OK(db_->Write(options, &batch));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +96,10 @@ class CorruptionTest {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
uint64_t key;
|
||||
Slice in(iter->key());
|
||||
if (in == "" || in == "~") {
|
||||
// Ignore boundary keys.
|
||||
continue;
|
||||
}
|
||||
if (!ConsumeDecimalNumber(&in, &key) ||
|
||||
!in.empty() ||
|
||||
key < next_expected) {
|
||||
@@ -123,7 +131,7 @@ class CorruptionTest {
|
||||
FileType type;
|
||||
std::string fname;
|
||||
int picked_number = -1;
|
||||
for (int i = 0; i < filenames.size(); i++) {
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) &&
|
||||
type == filetype &&
|
||||
int(number) > picked_number) { // Pick latest file
|
||||
@@ -233,7 +241,23 @@ TEST(CorruptionTest, TableFile) {
|
||||
dbi->TEST_CompactRange(1, NULL, NULL);
|
||||
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
Check(99, 99);
|
||||
Check(90, 99);
|
||||
}
|
||||
|
||||
TEST(CorruptionTest, TableFileRepair) {
|
||||
options_.block_size = 2 * kValueSize; // Limit scope of corruption
|
||||
options_.paranoid_checks = true;
|
||||
Reopen();
|
||||
Build(100);
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
dbi->TEST_CompactMemTable();
|
||||
dbi->TEST_CompactRange(0, NULL, NULL);
|
||||
dbi->TEST_CompactRange(1, NULL, NULL);
|
||||
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
RepairDB();
|
||||
Reopen();
|
||||
Check(95, 99);
|
||||
}
|
||||
|
||||
TEST(CorruptionTest, TableFileIndexData) {
|
||||
@@ -299,7 +323,7 @@ TEST(CorruptionTest, CompactionInputError) {
|
||||
ASSERT_EQ(1, Property("leveldb.num-files-at-level" + NumberToString(last)));
|
||||
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
Check(9, 9);
|
||||
Check(5, 9);
|
||||
|
||||
// Force compactions by writing lots of values
|
||||
Build(10000);
|
||||
@@ -307,32 +331,23 @@ TEST(CorruptionTest, CompactionInputError) {
|
||||
}
|
||||
|
||||
TEST(CorruptionTest, CompactionInputErrorParanoid) {
|
||||
Options options;
|
||||
options.paranoid_checks = true;
|
||||
options.write_buffer_size = 1048576;
|
||||
Reopen(&options);
|
||||
options_.paranoid_checks = true;
|
||||
options_.write_buffer_size = 512 << 10;
|
||||
Reopen();
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
|
||||
// Fill levels >= 1 so memtable compaction outputs to level 1
|
||||
for (int level = 1; level < config::kNumLevels; level++) {
|
||||
dbi->Put(WriteOptions(), "", "begin");
|
||||
dbi->Put(WriteOptions(), "~", "end");
|
||||
// Make multiple inputs so we need to compact.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Build(10);
|
||||
dbi->TEST_CompactMemTable();
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
env_.SleepForMicroseconds(100000);
|
||||
}
|
||||
dbi->CompactRange(NULL, NULL);
|
||||
|
||||
Build(10);
|
||||
dbi->TEST_CompactMemTable();
|
||||
ASSERT_EQ(1, Property("leveldb.num-files-at-level0"));
|
||||
|
||||
Corrupt(kTableFile, 100, 1);
|
||||
Check(9, 9);
|
||||
|
||||
// Write must eventually fail because of corrupted table
|
||||
Status s;
|
||||
// Write must fail because of corrupted table
|
||||
std::string tmp1, tmp2;
|
||||
for (int i = 0; i < 10000 && s.ok(); i++) {
|
||||
s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2));
|
||||
}
|
||||
Status s = db_->Put(WriteOptions(), Key(5, &tmp1), Value(5, &tmp2));
|
||||
ASSERT_TRUE(!s.ok()) << "write did not fail in corrupted paranoid db";
|
||||
}
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ static int FLAGS_bloom_bits = -1;
|
||||
static bool FLAGS_use_existing_db = false;
|
||||
|
||||
// Use the db with the following name.
|
||||
static const char* FLAGS_db = "/tmp/dbbench";
|
||||
static const char* FLAGS_db = NULL;
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -128,7 +128,7 @@ class RandomGenerator {
|
||||
pos_ = 0;
|
||||
}
|
||||
|
||||
Slice Generate(int len) {
|
||||
Slice Generate(size_t len) {
|
||||
if (pos_ + len > data_.size()) {
|
||||
pos_ = 0;
|
||||
assert(len < data_.size());
|
||||
@@ -139,11 +139,11 @@ class RandomGenerator {
|
||||
};
|
||||
|
||||
static Slice TrimSpace(Slice s) {
|
||||
int start = 0;
|
||||
size_t start = 0;
|
||||
while (start < s.size() && isspace(s[start])) {
|
||||
start++;
|
||||
}
|
||||
int limit = s.size();
|
||||
size_t limit = s.size();
|
||||
while (limit > start && isspace(s[limit-1])) {
|
||||
limit--;
|
||||
}
|
||||
@@ -399,7 +399,7 @@ class Benchmark {
|
||||
heap_counter_(0) {
|
||||
std::vector<std::string> files;
|
||||
Env::Default()->GetChildren(FLAGS_db, &files);
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
if (Slice(files[i]).starts_with("heap-")) {
|
||||
Env::Default()->DeleteFile(std::string(FLAGS_db) + "/" + files[i]);
|
||||
}
|
||||
@@ -693,6 +693,7 @@ class Benchmark {
|
||||
options.create_if_missing = !FLAGS_use_existing_db;
|
||||
options.block_cache = cache_;
|
||||
options.write_buffer_size = FLAGS_write_buffer_size;
|
||||
options.max_open_files = FLAGS_open_files;
|
||||
options.filter_policy = filter_policy_;
|
||||
Status s = DB::Open(options, FLAGS_db, &db_);
|
||||
if (!s.ok()) {
|
||||
@@ -925,6 +926,7 @@ class Benchmark {
|
||||
int main(int argc, char** argv) {
|
||||
FLAGS_write_buffer_size = leveldb::Options().write_buffer_size;
|
||||
FLAGS_open_files = leveldb::Options().max_open_files;
|
||||
std::string default_db_path;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
double d;
|
||||
@@ -964,6 +966,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// Choose a location for the test database if none given with --db=<path>
|
||||
if (FLAGS_db == NULL) {
|
||||
leveldb::Env::Default()->GetTestDirectory(&default_db_path);
|
||||
default_db_path += "/dbbench";
|
||||
FLAGS_db = default_db_path.c_str();
|
||||
}
|
||||
|
||||
leveldb::Benchmark benchmark;
|
||||
benchmark.Run();
|
||||
return 0;
|
||||
|
||||
137
db/db_impl.cc
137
db/db_impl.cc
@@ -35,6 +35,8 @@
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
const int kNumNonTableCacheFiles = 10;
|
||||
|
||||
// Information kept for every waiting writer
|
||||
struct DBImpl::Writer {
|
||||
Status status;
|
||||
@@ -92,9 +94,9 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
Options result = src;
|
||||
result.comparator = icmp;
|
||||
result.filter_policy = (src.filter_policy != NULL) ? ipolicy : NULL;
|
||||
ClipToRange(&result.max_open_files, 20, 50000);
|
||||
ClipToRange(&result.write_buffer_size, 64<<10, 1<<30);
|
||||
ClipToRange(&result.block_size, 1<<10, 4<<20);
|
||||
ClipToRange(&result.max_open_files, 64 + kNumNonTableCacheFiles, 50000);
|
||||
ClipToRange(&result.write_buffer_size, 64<<10, 1<<30);
|
||||
ClipToRange(&result.block_size, 1<<10, 4<<20);
|
||||
if (result.info_log == NULL) {
|
||||
// Open a log file in the same directory as the db
|
||||
src.env->CreateDir(dbname); // In case it does not exist
|
||||
@@ -111,14 +113,14 @@ Options SanitizeOptions(const std::string& dbname,
|
||||
return result;
|
||||
}
|
||||
|
||||
DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
: env_(options.env),
|
||||
internal_comparator_(options.comparator),
|
||||
internal_filter_policy_(options.filter_policy),
|
||||
options_(SanitizeOptions(
|
||||
dbname, &internal_comparator_, &internal_filter_policy_, options)),
|
||||
owns_info_log_(options_.info_log != options.info_log),
|
||||
owns_cache_(options_.block_cache != options.block_cache),
|
||||
DBImpl::DBImpl(const Options& raw_options, const std::string& dbname)
|
||||
: env_(raw_options.env),
|
||||
internal_comparator_(raw_options.comparator),
|
||||
internal_filter_policy_(raw_options.filter_policy),
|
||||
options_(SanitizeOptions(dbname, &internal_comparator_,
|
||||
&internal_filter_policy_, raw_options)),
|
||||
owns_info_log_(options_.info_log != raw_options.info_log),
|
||||
owns_cache_(options_.block_cache != raw_options.block_cache),
|
||||
dbname_(dbname),
|
||||
db_lock_(NULL),
|
||||
shutting_down_(NULL),
|
||||
@@ -128,6 +130,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
logfile_(NULL),
|
||||
logfile_number_(0),
|
||||
log_(NULL),
|
||||
seed_(0),
|
||||
tmp_batch_(new WriteBatch),
|
||||
bg_compaction_scheduled_(false),
|
||||
manual_compaction_(NULL) {
|
||||
@@ -135,7 +138,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname)
|
||||
has_imm_.Release_Store(NULL);
|
||||
|
||||
// Reserve ten files or so for other uses and give the rest to TableCache.
|
||||
const int table_cache_size = options.max_open_files - 10;
|
||||
const int table_cache_size = options_.max_open_files - kNumNonTableCacheFiles;
|
||||
table_cache_ = new TableCache(dbname_, &options_, table_cache_size);
|
||||
|
||||
versions_ = new VersionSet(dbname_, &options_, table_cache_,
|
||||
@@ -213,6 +216,12 @@ void DBImpl::MaybeIgnoreError(Status* s) const {
|
||||
}
|
||||
|
||||
void DBImpl::DeleteObsoleteFiles() {
|
||||
if (!bg_error_.ok()) {
|
||||
// After a background error, we don't know whether a new version may
|
||||
// or may not have been committed, so we cannot safely garbage collect.
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a set of all of the live files
|
||||
std::set<uint64_t> live = pending_outputs_;
|
||||
versions_->AddLiveFiles(&live);
|
||||
@@ -310,16 +319,24 @@ Status DBImpl::Recover(VersionEdit* edit) {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
std::set<uint64_t> expected;
|
||||
versions_->AddLiveFiles(&expected);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
std::vector<uint64_t> logs;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type)
|
||||
&& type == kLogFile
|
||||
&& ((number >= min_log) || (number == prev_log))) {
|
||||
logs.push_back(number);
|
||||
if (ParseFileName(filenames[i], &number, &type)) {
|
||||
expected.erase(number);
|
||||
if (type == kLogFile && ((number >= min_log) || (number == prev_log)))
|
||||
logs.push_back(number);
|
||||
}
|
||||
}
|
||||
if (!expected.empty()) {
|
||||
char buf[50];
|
||||
snprintf(buf, sizeof(buf), "%d missing files; e.g.",
|
||||
static_cast<int>(expected.size()));
|
||||
return Status::Corruption(buf, TableFileName(dbname_, *(expected.begin())));
|
||||
}
|
||||
|
||||
// Recover in the order in which the logs were generated
|
||||
std::sort(logs.begin(), logs.end());
|
||||
@@ -483,7 +500,7 @@ Status DBImpl::WriteLevel0Table(MemTable* mem, VersionEdit* edit,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CompactMemTable() {
|
||||
void DBImpl::CompactMemTable() {
|
||||
mutex_.AssertHeld();
|
||||
assert(imm_ != NULL);
|
||||
|
||||
@@ -511,9 +528,9 @@ Status DBImpl::CompactMemTable() {
|
||||
imm_ = NULL;
|
||||
has_imm_.Release_Store(NULL);
|
||||
DeleteObsoleteFiles();
|
||||
} else {
|
||||
RecordBackgroundError(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::CompactRange(const Slice* begin, const Slice* end) {
|
||||
@@ -556,16 +573,18 @@ void DBImpl::TEST_CompactRange(int level, const Slice* begin,const Slice* end) {
|
||||
}
|
||||
|
||||
MutexLock l(&mutex_);
|
||||
while (!manual.done) {
|
||||
while (manual_compaction_ != NULL) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
manual_compaction_ = &manual;
|
||||
MaybeScheduleCompaction();
|
||||
while (manual_compaction_ == &manual) {
|
||||
while (!manual.done && !shutting_down_.Acquire_Load() && bg_error_.ok()) {
|
||||
if (manual_compaction_ == NULL) { // Idle
|
||||
manual_compaction_ = &manual;
|
||||
MaybeScheduleCompaction();
|
||||
} else { // Running either my compaction or another compaction.
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
if (manual_compaction_ == &manual) {
|
||||
// Cancel my manual compaction since we aborted early for some reason.
|
||||
manual_compaction_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_CompactMemTable() {
|
||||
@@ -584,12 +603,22 @@ Status DBImpl::TEST_CompactMemTable() {
|
||||
return s;
|
||||
}
|
||||
|
||||
void DBImpl::RecordBackgroundError(const Status& s) {
|
||||
mutex_.AssertHeld();
|
||||
if (bg_error_.ok()) {
|
||||
bg_error_ = s;
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::MaybeScheduleCompaction() {
|
||||
mutex_.AssertHeld();
|
||||
if (bg_compaction_scheduled_) {
|
||||
// Already scheduled
|
||||
} else if (shutting_down_.Acquire_Load()) {
|
||||
// DB is being deleted; no more background compactions
|
||||
} else if (!bg_error_.ok()) {
|
||||
// Already got an error; no more changes
|
||||
} else if (imm_ == NULL &&
|
||||
manual_compaction_ == NULL &&
|
||||
!versions_->NeedsCompaction()) {
|
||||
@@ -607,9 +636,14 @@ void DBImpl::BGWork(void* db) {
|
||||
void DBImpl::BackgroundCall() {
|
||||
MutexLock l(&mutex_);
|
||||
assert(bg_compaction_scheduled_);
|
||||
if (!shutting_down_.Acquire_Load()) {
|
||||
if (shutting_down_.Acquire_Load()) {
|
||||
// No more background work when shutting down.
|
||||
} else if (!bg_error_.ok()) {
|
||||
// No more background work after a background error.
|
||||
} else {
|
||||
BackgroundCompaction();
|
||||
}
|
||||
|
||||
bg_compaction_scheduled_ = false;
|
||||
|
||||
// Previous compaction may have produced too many files in a level,
|
||||
@@ -657,6 +691,9 @@ void DBImpl::BackgroundCompaction() {
|
||||
c->edit()->AddFile(c->level() + 1, f->number, f->file_size,
|
||||
f->smallest, f->largest);
|
||||
status = versions_->LogAndApply(c->edit(), &mutex_);
|
||||
if (!status.ok()) {
|
||||
RecordBackgroundError(status);
|
||||
}
|
||||
VersionSet::LevelSummaryStorage tmp;
|
||||
Log(options_.info_log, "Moved #%lld to level-%d %lld bytes %s: %s\n",
|
||||
static_cast<unsigned long long>(f->number),
|
||||
@@ -667,6 +704,9 @@ void DBImpl::BackgroundCompaction() {
|
||||
} else {
|
||||
CompactionState* compact = new CompactionState(c);
|
||||
status = DoCompactionWork(compact);
|
||||
if (!status.ok()) {
|
||||
RecordBackgroundError(status);
|
||||
}
|
||||
CleanupCompaction(compact);
|
||||
c->ReleaseInputs();
|
||||
DeleteObsoleteFiles();
|
||||
@@ -680,9 +720,6 @@ void DBImpl::BackgroundCompaction() {
|
||||
} else {
|
||||
Log(options_.info_log,
|
||||
"Compaction error: %s", status.ToString().c_str());
|
||||
if (options_.paranoid_checks && bg_error_.ok()) {
|
||||
bg_error_ = status;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_manual) {
|
||||
@@ -967,6 +1004,9 @@ Status DBImpl::DoCompactionWork(CompactionState* compact) {
|
||||
if (status.ok()) {
|
||||
status = InstallCompactionResults(compact);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
RecordBackgroundError(status);
|
||||
}
|
||||
VersionSet::LevelSummaryStorage tmp;
|
||||
Log(options_.info_log,
|
||||
"compacted to: %s", versions_->LevelSummary(&tmp));
|
||||
@@ -993,7 +1033,8 @@ static void CleanupIteratorState(void* arg1, void* arg2) {
|
||||
} // namespace
|
||||
|
||||
Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
|
||||
SequenceNumber* latest_snapshot) {
|
||||
SequenceNumber* latest_snapshot,
|
||||
uint32_t* seed) {
|
||||
IterState* cleanup = new IterState;
|
||||
mutex_.Lock();
|
||||
*latest_snapshot = versions_->LastSequence();
|
||||
@@ -1017,13 +1058,15 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options,
|
||||
cleanup->version = versions_->current();
|
||||
internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, NULL);
|
||||
|
||||
*seed = ++seed_;
|
||||
mutex_.Unlock();
|
||||
return internal_iter;
|
||||
}
|
||||
|
||||
Iterator* DBImpl::TEST_NewInternalIterator() {
|
||||
SequenceNumber ignored;
|
||||
return NewInternalIterator(ReadOptions(), &ignored);
|
||||
uint32_t ignored_seed;
|
||||
return NewInternalIterator(ReadOptions(), &ignored, &ignored_seed);
|
||||
}
|
||||
|
||||
int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes() {
|
||||
@@ -1080,12 +1123,21 @@ Status DBImpl::Get(const ReadOptions& options,
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& options) {
|
||||
SequenceNumber latest_snapshot;
|
||||
Iterator* internal_iter = NewInternalIterator(options, &latest_snapshot);
|
||||
uint32_t seed;
|
||||
Iterator* iter = NewInternalIterator(options, &latest_snapshot, &seed);
|
||||
return NewDBIterator(
|
||||
&dbname_, env_, user_comparator(), internal_iter,
|
||||
this, user_comparator(), iter,
|
||||
(options.snapshot != NULL
|
||||
? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_
|
||||
: latest_snapshot));
|
||||
: latest_snapshot),
|
||||
seed);
|
||||
}
|
||||
|
||||
void DBImpl::RecordReadSample(Slice key) {
|
||||
MutexLock l(&mutex_);
|
||||
if (versions_->current()->RecordReadSample(key)) {
|
||||
MaybeScheduleCompaction();
|
||||
}
|
||||
}
|
||||
|
||||
const Snapshot* DBImpl::GetSnapshot() {
|
||||
@@ -1138,13 +1190,23 @@ Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
|
||||
{
|
||||
mutex_.Unlock();
|
||||
status = log_->AddRecord(WriteBatchInternal::Contents(updates));
|
||||
bool sync_error = false;
|
||||
if (status.ok() && options.sync) {
|
||||
status = logfile_->Sync();
|
||||
if (!status.ok()) {
|
||||
sync_error = true;
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = WriteBatchInternal::InsertInto(updates, mem_);
|
||||
}
|
||||
mutex_.Lock();
|
||||
if (sync_error) {
|
||||
// The state of the log file is indeterminate: the log record we
|
||||
// just added may or may not show up when the DB is re-opened.
|
||||
// So we force the DB into a mode where all future writes fail.
|
||||
RecordBackgroundError(status);
|
||||
}
|
||||
}
|
||||
if (updates == tmp_batch_) tmp_batch_->Clear();
|
||||
|
||||
@@ -1251,10 +1313,11 @@ Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
} else if (imm_ != NULL) {
|
||||
// We have filled up the current memtable, but the previous
|
||||
// one is still being compacted, so we wait.
|
||||
Log(options_.info_log, "Current memtable full; waiting...\n");
|
||||
bg_cv_.Wait();
|
||||
} else if (versions_->NumLevelFiles(0) >= config::kL0_StopWritesTrigger) {
|
||||
// There are too many level-0 files.
|
||||
Log(options_.info_log, "waiting...\n");
|
||||
Log(options_.info_log, "Too many L0 files; waiting...\n");
|
||||
bg_cv_.Wait();
|
||||
} else {
|
||||
// Attempt to switch to a new memtable and trigger compaction of old
|
||||
@@ -1263,6 +1326,8 @@ Status DBImpl::MakeRoomForWrite(bool force) {
|
||||
WritableFile* lfile = NULL;
|
||||
s = env_->NewWritableFile(LogFileName(dbname_, new_log_number), &lfile);
|
||||
if (!s.ok()) {
|
||||
// Avoid chewing through file number space in a tight loop.
|
||||
versions_->ReuseFileNumber(new_log_number);
|
||||
break;
|
||||
}
|
||||
delete log_;
|
||||
|
||||
39
db/db_impl.h
39
db/db_impl.h
@@ -13,6 +13,7 @@
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -58,20 +59,26 @@ class DBImpl : public DB {
|
||||
// file at a level >= 1.
|
||||
int64_t TEST_MaxNextLevelOverlappingBytes();
|
||||
|
||||
// Record a sample of bytes read at the specified internal key.
|
||||
// Samples are taken approximately once every config::kReadBytesPeriod
|
||||
// bytes.
|
||||
void RecordReadSample(Slice key);
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
struct CompactionState;
|
||||
struct Writer;
|
||||
|
||||
Iterator* NewInternalIterator(const ReadOptions&,
|
||||
SequenceNumber* latest_snapshot);
|
||||
SequenceNumber* latest_snapshot,
|
||||
uint32_t* seed);
|
||||
|
||||
Status NewDB();
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
Status Recover(VersionEdit* edit);
|
||||
Status Recover(VersionEdit* edit) EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void MaybeIgnoreError(Status* s) const;
|
||||
|
||||
@@ -80,27 +87,36 @@ class DBImpl : public DB {
|
||||
|
||||
// Compact the in-memory write buffer to disk. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful.
|
||||
Status CompactMemTable();
|
||||
// Errors are recorded in bg_error_.
|
||||
void CompactMemTable() EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Status RecoverLogFile(uint64_t log_number,
|
||||
VersionEdit* edit,
|
||||
SequenceNumber* max_sequence);
|
||||
SequenceNumber* max_sequence)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Status WriteLevel0Table(MemTable* mem, VersionEdit* edit, Version* base);
|
||||
Status WriteLevel0Table(MemTable* mem, VersionEdit* edit, Version* base)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Status MakeRoomForWrite(bool force /* compact even if there is room? */);
|
||||
Status MakeRoomForWrite(bool force /* compact even if there is room? */)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
WriteBatch* BuildBatchGroup(Writer** last_writer);
|
||||
|
||||
void MaybeScheduleCompaction();
|
||||
void RecordBackgroundError(const Status& s);
|
||||
|
||||
void MaybeScheduleCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
static void BGWork(void* db);
|
||||
void BackgroundCall();
|
||||
void BackgroundCompaction();
|
||||
void CleanupCompaction(CompactionState* compact);
|
||||
Status DoCompactionWork(CompactionState* compact);
|
||||
void BackgroundCompaction() EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
void CleanupCompaction(CompactionState* compact)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
Status DoCompactionWork(CompactionState* compact)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Status OpenCompactionOutputFile(CompactionState* compact);
|
||||
Status FinishCompactionOutputFile(CompactionState* compact, Iterator* input);
|
||||
Status InstallCompactionResults(CompactionState* compact);
|
||||
Status InstallCompactionResults(CompactionState* compact)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Constant after construction
|
||||
Env* const env_;
|
||||
@@ -127,6 +143,7 @@ class DBImpl : public DB {
|
||||
WritableFile* logfile_;
|
||||
uint64_t logfile_number_;
|
||||
log::Writer* log_;
|
||||
uint32_t seed_; // For sampling.
|
||||
|
||||
// Queue of writers.
|
||||
std::deque<Writer*> writers_;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
#include "db/db_iter.h"
|
||||
|
||||
#include "db/filename.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/iterator.h"
|
||||
#include "port/port.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -46,15 +48,16 @@ class DBIter: public Iterator {
|
||||
kReverse
|
||||
};
|
||||
|
||||
DBIter(const std::string* dbname, Env* env,
|
||||
const Comparator* cmp, Iterator* iter, SequenceNumber s)
|
||||
: dbname_(dbname),
|
||||
env_(env),
|
||||
DBIter(DBImpl* db, const Comparator* cmp, Iterator* iter, SequenceNumber s,
|
||||
uint32_t seed)
|
||||
: db_(db),
|
||||
user_comparator_(cmp),
|
||||
iter_(iter),
|
||||
sequence_(s),
|
||||
direction_(kForward),
|
||||
valid_(false) {
|
||||
valid_(false),
|
||||
rnd_(seed),
|
||||
bytes_counter_(RandomPeriod()) {
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
delete iter_;
|
||||
@@ -100,8 +103,12 @@ class DBIter: public Iterator {
|
||||
}
|
||||
}
|
||||
|
||||
const std::string* const dbname_;
|
||||
Env* const env_;
|
||||
// Pick next gap with average value of config::kReadBytesPeriod.
|
||||
ssize_t RandomPeriod() {
|
||||
return rnd_.Uniform(2*config::kReadBytesPeriod);
|
||||
}
|
||||
|
||||
DBImpl* db_;
|
||||
const Comparator* const user_comparator_;
|
||||
Iterator* const iter_;
|
||||
SequenceNumber const sequence_;
|
||||
@@ -112,13 +119,23 @@ class DBIter: public Iterator {
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
|
||||
Random rnd_;
|
||||
ssize_t bytes_counter_;
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&);
|
||||
void operator=(const DBIter&);
|
||||
};
|
||||
|
||||
inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
if (!ParseInternalKey(iter_->key(), ikey)) {
|
||||
Slice k = iter_->key();
|
||||
ssize_t n = k.size() + iter_->value().size();
|
||||
bytes_counter_ -= n;
|
||||
while (bytes_counter_ < 0) {
|
||||
bytes_counter_ += RandomPeriod();
|
||||
db_->RecordReadSample(k);
|
||||
}
|
||||
if (!ParseInternalKey(k, ikey)) {
|
||||
status_ = Status::Corruption("corrupted internal key in DBIter");
|
||||
return false;
|
||||
} else {
|
||||
@@ -144,12 +161,13 @@ void DBIter::Next() {
|
||||
saved_key_.clear();
|
||||
return;
|
||||
}
|
||||
// saved_key_ already contains the key to skip past.
|
||||
} else {
|
||||
// Store in saved_key_ the current key so we skip it below.
|
||||
SaveKey(ExtractUserKey(iter_->key()), &saved_key_);
|
||||
}
|
||||
|
||||
// Temporarily use saved_key_ as storage for key to skip.
|
||||
std::string* skip = &saved_key_;
|
||||
SaveKey(ExtractUserKey(iter_->key()), skip);
|
||||
FindNextUserEntry(true, skip);
|
||||
FindNextUserEntry(true, &saved_key_);
|
||||
}
|
||||
|
||||
void DBIter::FindNextUserEntry(bool skipping, std::string* skip) {
|
||||
@@ -288,12 +306,12 @@ void DBIter::SeekToLast() {
|
||||
} // anonymous namespace
|
||||
|
||||
Iterator* NewDBIterator(
|
||||
const std::string* dbname,
|
||||
Env* env,
|
||||
DBImpl* db,
|
||||
const Comparator* user_key_comparator,
|
||||
Iterator* internal_iter,
|
||||
const SequenceNumber& sequence) {
|
||||
return new DBIter(dbname, env, user_key_comparator, internal_iter, sequence);
|
||||
SequenceNumber sequence,
|
||||
uint32_t seed) {
|
||||
return new DBIter(db, user_key_comparator, internal_iter, sequence, seed);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -11,15 +11,17 @@
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class DBImpl;
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
// "*internal_iter") that were live at the specified "sequence" number
|
||||
// into appropriate user keys.
|
||||
extern Iterator* NewDBIterator(
|
||||
const std::string* dbname,
|
||||
Env* env,
|
||||
DBImpl* db,
|
||||
const Comparator* user_key_comparator,
|
||||
Iterator* internal_iter,
|
||||
const SequenceNumber& sequence);
|
||||
SequenceNumber sequence,
|
||||
uint32_t seed);
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
|
||||
287
db/db_test.cc
287
db/db_test.cc
@@ -33,8 +33,11 @@ class AtomicCounter {
|
||||
public:
|
||||
AtomicCounter() : count_(0) { }
|
||||
void Increment() {
|
||||
IncrementBy(1);
|
||||
}
|
||||
void IncrementBy(int count) {
|
||||
MutexLock l(&mu_);
|
||||
count_++;
|
||||
count_ += count;
|
||||
}
|
||||
int Read() {
|
||||
MutexLock l(&mu_);
|
||||
@@ -45,38 +48,58 @@ class AtomicCounter {
|
||||
count_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
void DelayMilliseconds(int millis) {
|
||||
Env::Default()->SleepForMicroseconds(millis * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Special Env used to delay background operations
|
||||
class SpecialEnv : public EnvWrapper {
|
||||
public:
|
||||
// sstable Sync() calls are blocked while this pointer is non-NULL.
|
||||
port::AtomicPointer delay_sstable_sync_;
|
||||
// sstable/log Sync() calls are blocked while this pointer is non-NULL.
|
||||
port::AtomicPointer delay_data_sync_;
|
||||
|
||||
// sstable/log Sync() calls return an error.
|
||||
port::AtomicPointer data_sync_error_;
|
||||
|
||||
// Simulate no-space errors while this pointer is non-NULL.
|
||||
port::AtomicPointer no_space_;
|
||||
|
||||
// Simulate non-writable file system while this pointer is non-NULL
|
||||
port::AtomicPointer non_writable_;
|
||||
|
||||
// Force sync of manifest files to fail while this pointer is non-NULL
|
||||
port::AtomicPointer manifest_sync_error_;
|
||||
|
||||
// Force write to manifest files to fail while this pointer is non-NULL
|
||||
port::AtomicPointer manifest_write_error_;
|
||||
|
||||
bool count_random_reads_;
|
||||
AtomicCounter random_read_counter_;
|
||||
|
||||
explicit SpecialEnv(Env* base) : EnvWrapper(base) {
|
||||
delay_sstable_sync_.Release_Store(NULL);
|
||||
delay_data_sync_.Release_Store(NULL);
|
||||
data_sync_error_.Release_Store(NULL);
|
||||
no_space_.Release_Store(NULL);
|
||||
non_writable_.Release_Store(NULL);
|
||||
count_random_reads_ = false;
|
||||
manifest_sync_error_.Release_Store(NULL);
|
||||
manifest_write_error_.Release_Store(NULL);
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& f, WritableFile** r) {
|
||||
class SSTableFile : public WritableFile {
|
||||
class DataFile : public WritableFile {
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
WritableFile* base_;
|
||||
|
||||
public:
|
||||
SSTableFile(SpecialEnv* env, WritableFile* base)
|
||||
DataFile(SpecialEnv* env, WritableFile* base)
|
||||
: env_(env),
|
||||
base_(base) {
|
||||
}
|
||||
~SSTableFile() { delete base_; }
|
||||
~DataFile() { delete base_; }
|
||||
Status Append(const Slice& data) {
|
||||
if (env_->no_space_.Acquire_Load() != NULL) {
|
||||
// Drop writes on the floor
|
||||
@@ -88,17 +111,51 @@ class SpecialEnv : public EnvWrapper {
|
||||
Status Close() { return base_->Close(); }
|
||||
Status Flush() { return base_->Flush(); }
|
||||
Status Sync() {
|
||||
while (env_->delay_sstable_sync_.Acquire_Load() != NULL) {
|
||||
env_->SleepForMicroseconds(100000);
|
||||
if (env_->data_sync_error_.Acquire_Load() != NULL) {
|
||||
return Status::IOError("simulated data sync error");
|
||||
}
|
||||
while (env_->delay_data_sync_.Acquire_Load() != NULL) {
|
||||
DelayMilliseconds(100);
|
||||
}
|
||||
return base_->Sync();
|
||||
}
|
||||
};
|
||||
class ManifestFile : public WritableFile {
|
||||
private:
|
||||
SpecialEnv* env_;
|
||||
WritableFile* base_;
|
||||
public:
|
||||
ManifestFile(SpecialEnv* env, WritableFile* b) : env_(env), base_(b) { }
|
||||
~ManifestFile() { delete base_; }
|
||||
Status Append(const Slice& data) {
|
||||
if (env_->manifest_write_error_.Acquire_Load() != NULL) {
|
||||
return Status::IOError("simulated writer error");
|
||||
} else {
|
||||
return base_->Append(data);
|
||||
}
|
||||
}
|
||||
Status Close() { return base_->Close(); }
|
||||
Status Flush() { return base_->Flush(); }
|
||||
Status Sync() {
|
||||
if (env_->manifest_sync_error_.Acquire_Load() != NULL) {
|
||||
return Status::IOError("simulated sync error");
|
||||
} else {
|
||||
return base_->Sync();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (non_writable_.Acquire_Load() != NULL) {
|
||||
return Status::IOError("simulated write error");
|
||||
}
|
||||
|
||||
Status s = target()->NewWritableFile(f, r);
|
||||
if (s.ok()) {
|
||||
if (strstr(f.c_str(), ".sst") != NULL) {
|
||||
*r = new SSTableFile(this, *r);
|
||||
if (strstr(f.c_str(), ".ldb") != NULL ||
|
||||
strstr(f.c_str(), ".log") != NULL) {
|
||||
*r = new DataFile(this, *r);
|
||||
} else if (strstr(f.c_str(), "MANIFEST") != NULL) {
|
||||
*r = new ManifestFile(this, *r);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
@@ -137,6 +194,7 @@ class DBTest {
|
||||
enum OptionConfig {
|
||||
kDefault,
|
||||
kFilter,
|
||||
kUncompressed,
|
||||
kEnd
|
||||
};
|
||||
int option_config_;
|
||||
@@ -167,10 +225,10 @@ class DBTest {
|
||||
// Switch to a fresh database with the next option configuration to
|
||||
// test. Return false if there are no more configurations to test.
|
||||
bool ChangeOptions() {
|
||||
if (option_config_ == kEnd) {
|
||||
option_config_++;
|
||||
if (option_config_ >= kEnd) {
|
||||
return false;
|
||||
} else {
|
||||
option_config_++;
|
||||
DestroyAndReopen();
|
||||
return true;
|
||||
}
|
||||
@@ -183,6 +241,9 @@ class DBTest {
|
||||
case kFilter:
|
||||
options.filter_policy = filter_policy_;
|
||||
break;
|
||||
case kUncompressed:
|
||||
options.compression = kNoCompression;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -260,7 +321,7 @@ class DBTest {
|
||||
}
|
||||
|
||||
// Check reverse iteration results are the reverse of forward results
|
||||
int matched = 0;
|
||||
size_t matched = 0;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_LT(matched, forward.size());
|
||||
ASSERT_EQ(IterStatus(iter), forward[forward.size() - matched - 1]);
|
||||
@@ -408,6 +469,38 @@ class DBTest {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DeleteAnSSTFile() {
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == kTableFile) {
|
||||
ASSERT_OK(env_->DeleteFile(TableFileName(dbname_, number)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns number of files renamed.
|
||||
int RenameLDBToSST() {
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
int files_renamed = 0;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == kTableFile) {
|
||||
const std::string from = TableFileName(dbname_, number);
|
||||
const std::string to = SSTTableFileName(dbname_, number);
|
||||
ASSERT_OK(env_->RenameFile(from, to));
|
||||
files_renamed++;
|
||||
}
|
||||
}
|
||||
return files_renamed;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(DBTest, Empty) {
|
||||
@@ -449,11 +542,11 @@ TEST(DBTest, GetFromImmutableLayer) {
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
|
||||
env_->delay_sstable_sync_.Release_Store(env_); // Block sync calls
|
||||
env_->delay_data_sync_.Release_Store(env_); // Block sync calls
|
||||
Put("k1", std::string(100000, 'x')); // Fill memtable
|
||||
Put("k2", std::string(100000, 'y')); // Trigger compaction
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
env_->delay_sstable_sync_.Release_Store(NULL); // Release sync calls
|
||||
env_->delay_data_sync_.Release_Store(NULL); // Release sync calls
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
@@ -552,13 +645,15 @@ TEST(DBTest, GetEncountersEmptyLevel) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 1);
|
||||
|
||||
// Step 3: read until level 0 compaction disappears.
|
||||
int read_count = 0;
|
||||
while (NumTableFilesAtLevel(0) > 0) {
|
||||
ASSERT_LE(read_count, 10000) << "did not trigger level 0 compaction";
|
||||
read_count++;
|
||||
// Step 3: read a bunch of times
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("missing"));
|
||||
}
|
||||
|
||||
// Step 4: Wait for compaction to finish
|
||||
DelayMilliseconds(1000);
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
@@ -1240,7 +1335,7 @@ TEST(DBTest, L0_CompactionBug_Issue44_a) {
|
||||
Reopen();
|
||||
Reopen();
|
||||
ASSERT_EQ("(a->v)", Contents());
|
||||
env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
|
||||
DelayMilliseconds(1000); // Wait for compaction to finish
|
||||
ASSERT_EQ("(a->v)", Contents());
|
||||
}
|
||||
|
||||
@@ -1256,7 +1351,7 @@ TEST(DBTest, L0_CompactionBug_Issue44_b) {
|
||||
Put("","");
|
||||
Reopen();
|
||||
Put("","");
|
||||
env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
|
||||
DelayMilliseconds(1000); // Wait for compaction to finish
|
||||
Reopen();
|
||||
Put("d","dv");
|
||||
Reopen();
|
||||
@@ -1266,7 +1361,7 @@ TEST(DBTest, L0_CompactionBug_Issue44_b) {
|
||||
Delete("b");
|
||||
Reopen();
|
||||
ASSERT_EQ("(->)(c->cv)", Contents());
|
||||
env_->SleepForMicroseconds(1000000); // Wait for compaction to finish
|
||||
DelayMilliseconds(1000); // Wait for compaction to finish
|
||||
ASSERT_EQ("(->)(c->cv)", Contents());
|
||||
}
|
||||
|
||||
@@ -1421,6 +1516,12 @@ TEST(DBTest, DBOpen_Options) {
|
||||
db = NULL;
|
||||
}
|
||||
|
||||
TEST(DBTest, Locking) {
|
||||
DB* db2 = NULL;
|
||||
Status s = DB::Open(CurrentOptions(), dbname_, &db2);
|
||||
ASSERT_TRUE(!s.ok()) << "Locking did not prevent re-opening db";
|
||||
}
|
||||
|
||||
// Check that number of files does not grow when we are out of space
|
||||
TEST(DBTest, NoSpace) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -1438,7 +1539,133 @@ TEST(DBTest, NoSpace) {
|
||||
}
|
||||
}
|
||||
env_->no_space_.Release_Store(NULL);
|
||||
ASSERT_LT(CountFiles(), num_files + 5);
|
||||
ASSERT_LT(CountFiles(), num_files + 3);
|
||||
}
|
||||
|
||||
TEST(DBTest, NonWritableFileSystem) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 1000;
|
||||
options.env = env_;
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
env_->non_writable_.Release_Store(env_); // Force errors for new files
|
||||
std::string big(100000, 'x');
|
||||
int errors = 0;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
fprintf(stderr, "iter %d; errors %d\n", i, errors);
|
||||
if (!Put("foo", big).ok()) {
|
||||
errors++;
|
||||
DelayMilliseconds(100);
|
||||
}
|
||||
}
|
||||
ASSERT_GT(errors, 0);
|
||||
env_->non_writable_.Release_Store(NULL);
|
||||
}
|
||||
|
||||
TEST(DBTest, WriteSyncError) {
|
||||
// Check that log sync errors cause the DB to disallow future writes.
|
||||
|
||||
// (a) Cause log sync calls to fail
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
Reopen(&options);
|
||||
env_->data_sync_error_.Release_Store(env_);
|
||||
|
||||
// (b) Normal write should succeed
|
||||
WriteOptions w;
|
||||
ASSERT_OK(db_->Put(w, "k1", "v1"));
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
|
||||
// (c) Do a sync write; should fail
|
||||
w.sync = true;
|
||||
ASSERT_TRUE(!db_->Put(w, "k2", "v2").ok());
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("k2"));
|
||||
|
||||
// (d) make sync behave normally
|
||||
env_->data_sync_error_.Release_Store(NULL);
|
||||
|
||||
// (e) Do a non-sync write; should fail
|
||||
w.sync = false;
|
||||
ASSERT_TRUE(!db_->Put(w, "k3", "v3").ok());
|
||||
ASSERT_EQ("v1", Get("k1"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("k2"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("k3"));
|
||||
}
|
||||
|
||||
TEST(DBTest, ManifestWriteError) {
|
||||
// Test for the following problem:
|
||||
// (a) Compaction produces file F
|
||||
// (b) Log record containing F is written to MANIFEST file, but Sync() fails
|
||||
// (c) GC deletes F
|
||||
// (d) After reopening DB, reads fail since deleted F is named in log record
|
||||
|
||||
// We iterate twice. In the second iteration, everything is the
|
||||
// same except the log record never makes it to the MANIFEST file.
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
port::AtomicPointer* error_type = (iter == 0)
|
||||
? &env_->manifest_sync_error_
|
||||
: &env_->manifest_write_error_;
|
||||
|
||||
// Insert foo=>bar mapping
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
DestroyAndReopen(&options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Memtable compaction (will succeed)
|
||||
dbfull()->TEST_CompactMemTable();
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
const int last = config::kMaxMemCompactLevel;
|
||||
ASSERT_EQ(NumTableFilesAtLevel(last), 1); // foo=>bar is now in last level
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->Release_Store(env_);
|
||||
dbfull()->TEST_CompactRange(last, NULL, NULL); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
error_type->Release_Store(NULL);
|
||||
Reopen(&options);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(DBTest, MissingSSTFile) {
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Dump the memtable to disk.
|
||||
dbfull()->TEST_CompactMemTable();
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
Close();
|
||||
ASSERT_TRUE(DeleteAnSSTFile());
|
||||
Options options = CurrentOptions();
|
||||
options.paranoid_checks = true;
|
||||
Status s = TryReopen(&options);
|
||||
ASSERT_TRUE(!s.ok());
|
||||
ASSERT_TRUE(s.ToString().find("issing") != std::string::npos)
|
||||
<< s.ToString();
|
||||
}
|
||||
|
||||
TEST(DBTest, StillReadSST) {
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Dump the memtable to disk.
|
||||
dbfull()->TEST_CompactMemTable();
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
Close();
|
||||
ASSERT_GT(RenameLDBToSST(), 0);
|
||||
Options options = CurrentOptions();
|
||||
options.paranoid_checks = true;
|
||||
Status s = TryReopen(&options);
|
||||
ASSERT_TRUE(s.ok());
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
}
|
||||
|
||||
TEST(DBTest, FilesDeletedAfterCompaction) {
|
||||
@@ -1472,7 +1699,7 @@ TEST(DBTest, BloomFilter) {
|
||||
dbfull()->TEST_CompactMemTable();
|
||||
|
||||
// Prevent auto compactions triggered by seeks
|
||||
env_->delay_sstable_sync_.Release_Store(env_);
|
||||
env_->delay_data_sync_.Release_Store(env_);
|
||||
|
||||
// Lookup present keys. Should rarely read from small sstable.
|
||||
env_->random_read_counter_.Reset();
|
||||
@@ -1493,7 +1720,7 @@ TEST(DBTest, BloomFilter) {
|
||||
fprintf(stderr, "%d missing => %d reads\n", N, reads);
|
||||
ASSERT_LE(reads, 3*N/100);
|
||||
|
||||
env_->delay_sstable_sync_.Release_Store(NULL);
|
||||
env_->delay_data_sync_.Release_Store(NULL);
|
||||
Close();
|
||||
delete options.block_cache;
|
||||
delete options.filter_policy;
|
||||
@@ -1553,7 +1780,7 @@ static void MTThreadBody(void* arg) {
|
||||
ASSERT_EQ(k, key);
|
||||
ASSERT_GE(w, 0);
|
||||
ASSERT_LT(w, kNumThreads);
|
||||
ASSERT_LE(c, reinterpret_cast<uintptr_t>(
|
||||
ASSERT_LE(static_cast<uintptr_t>(c), reinterpret_cast<uintptr_t>(
|
||||
t->state->counter[w].Acquire_Load()));
|
||||
}
|
||||
}
|
||||
@@ -1585,13 +1812,13 @@ TEST(DBTest, MultiThreaded) {
|
||||
}
|
||||
|
||||
// Let them run for a while
|
||||
env_->SleepForMicroseconds(kTestSeconds * 1000000);
|
||||
DelayMilliseconds(kTestSeconds * 1000);
|
||||
|
||||
// Stop the threads and wait for them to finish
|
||||
mt.stop.Release_Store(&mt);
|
||||
for (int id = 0; id < kNumThreads; id++) {
|
||||
while (mt.thread_done[id].Acquire_Load() == NULL) {
|
||||
env_->SleepForMicroseconds(100000);
|
||||
DelayMilliseconds(100);
|
||||
}
|
||||
}
|
||||
} while (ChangeOptions());
|
||||
|
||||
@@ -26,7 +26,7 @@ std::string ParsedInternalKey::DebugString() const {
|
||||
(unsigned long long) sequence,
|
||||
int(type));
|
||||
std::string result = "'";
|
||||
result += user_key.ToString();
|
||||
result += EscapeString(user_key.ToString());
|
||||
result += buf;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ static const int kL0_StopWritesTrigger = 12;
|
||||
// space if the same key space is being repeatedly overwritten.
|
||||
static const int kMaxMemCompactLevel = 2;
|
||||
|
||||
// Approximate gap in bytes between samples of data read during iteration.
|
||||
static const int kReadBytesPeriod = 1048576;
|
||||
|
||||
} // namespace config
|
||||
|
||||
class InternalKey;
|
||||
|
||||
@@ -30,6 +30,11 @@ std::string LogFileName(const std::string& name, uint64_t number) {
|
||||
}
|
||||
|
||||
std::string TableFileName(const std::string& name, uint64_t number) {
|
||||
assert(number > 0);
|
||||
return MakeFileName(name, number, "ldb");
|
||||
}
|
||||
|
||||
std::string SSTTableFileName(const std::string& name, uint64_t number) {
|
||||
assert(number > 0);
|
||||
return MakeFileName(name, number, "sst");
|
||||
}
|
||||
@@ -71,7 +76,7 @@ std::string OldInfoLogFileName(const std::string& dbname) {
|
||||
// dbname/LOG
|
||||
// dbname/LOG.old
|
||||
// dbname/MANIFEST-[0-9]+
|
||||
// dbname/[0-9]+.(log|sst)
|
||||
// dbname/[0-9]+.(log|sst|ldb)
|
||||
bool ParseFileName(const std::string& fname,
|
||||
uint64_t* number,
|
||||
FileType* type) {
|
||||
@@ -106,7 +111,7 @@ bool ParseFileName(const std::string& fname,
|
||||
Slice suffix = rest;
|
||||
if (suffix == Slice(".log")) {
|
||||
*type = kLogFile;
|
||||
} else if (suffix == Slice(".sst")) {
|
||||
} else if (suffix == Slice(".sst") || suffix == Slice(".ldb")) {
|
||||
*type = kTableFile;
|
||||
} else if (suffix == Slice(".dbtmp")) {
|
||||
*type = kTempFile;
|
||||
|
||||
@@ -37,6 +37,11 @@ extern std::string LogFileName(const std::string& dbname, uint64_t number);
|
||||
// "dbname".
|
||||
extern std::string TableFileName(const std::string& dbname, uint64_t number);
|
||||
|
||||
// Return the legacy file name for an sstable with the specified number
|
||||
// in the db named by "dbname". The result will be prefixed with
|
||||
// "dbname".
|
||||
extern std::string SSTTableFileName(const std::string& dbname, uint64_t number);
|
||||
|
||||
// Return the name of the descriptor file for the db named by
|
||||
// "dbname" and the specified incarnation number. The result will be
|
||||
// prefixed with "dbname".
|
||||
|
||||
@@ -27,6 +27,7 @@ TEST(FileNameTest, Parse) {
|
||||
{ "100.log", 100, kLogFile },
|
||||
{ "0.log", 0, kLogFile },
|
||||
{ "0.sst", 0, kTableFile },
|
||||
{ "0.ldb", 0, kTableFile },
|
||||
{ "CURRENT", 0, kCurrentFile },
|
||||
{ "LOCK", 0, kDBLockFile },
|
||||
{ "MANIFEST-2", 2, kDescriptorFile },
|
||||
@@ -70,7 +71,7 @@ TEST(FileNameTest, Parse) {
|
||||
for (int i = 0; i < sizeof(errors) / sizeof(errors[0]); i++) {
|
||||
std::string f = errors[i];
|
||||
ASSERT_TRUE(!ParseFileName(f, &number, &type)) << f;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FileNameTest, Construction) {
|
||||
|
||||
238
db/leveldb_main.cc
Normal file
238
db/leveldb_main.cc
Normal file
@@ -0,0 +1,238 @@
|
||||
// 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 <stdio.h>
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "leveldb/env.h"
|
||||
#include "leveldb/iterator.h"
|
||||
#include "leveldb/options.h"
|
||||
#include "leveldb/status.h"
|
||||
#include "leveldb/table.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
namespace {
|
||||
|
||||
bool GuessType(const std::string& fname, FileType* type) {
|
||||
size_t pos = fname.rfind('/');
|
||||
std::string basename;
|
||||
if (pos == std::string::npos) {
|
||||
basename = fname;
|
||||
} else {
|
||||
basename = std::string(fname.data() + pos + 1, fname.size() - pos - 1);
|
||||
}
|
||||
uint64_t ignored;
|
||||
return ParseFileName(basename, &ignored, type);
|
||||
}
|
||||
|
||||
// Notified when log reader encounters corruption.
|
||||
class CorruptionReporter : public log::Reader::Reporter {
|
||||
public:
|
||||
virtual void Corruption(size_t bytes, const Status& status) {
|
||||
printf("corruption: %d bytes; %s\n",
|
||||
static_cast<int>(bytes),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
};
|
||||
|
||||
// Print contents of a log file. (*func)() is called on every record.
|
||||
bool PrintLogContents(Env* env, const std::string& fname,
|
||||
void (*func)(Slice)) {
|
||||
SequentialFile* file;
|
||||
Status s = env->NewSequentialFile(fname, &file);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "%s\n", s.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
CorruptionReporter reporter;
|
||||
log::Reader reader(file, &reporter, true, 0);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
while (reader.ReadRecord(&record, &scratch)) {
|
||||
printf("--- offset %llu; ",
|
||||
static_cast<unsigned long long>(reader.LastRecordOffset()));
|
||||
(*func)(record);
|
||||
}
|
||||
delete file;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Called on every item found in a WriteBatch.
|
||||
class WriteBatchItemPrinter : public WriteBatch::Handler {
|
||||
public:
|
||||
uint64_t offset_;
|
||||
uint64_t sequence_;
|
||||
|
||||
virtual void Put(const Slice& key, const Slice& value) {
|
||||
printf(" put '%s' '%s'\n",
|
||||
EscapeString(key).c_str(),
|
||||
EscapeString(value).c_str());
|
||||
}
|
||||
virtual void Delete(const Slice& key) {
|
||||
printf(" del '%s'\n",
|
||||
EscapeString(key).c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Called on every log record (each one of which is a WriteBatch)
|
||||
// found in a kLogFile.
|
||||
static void WriteBatchPrinter(Slice record) {
|
||||
if (record.size() < 12) {
|
||||
printf("log record length %d is too small\n",
|
||||
static_cast<int>(record.size()));
|
||||
return;
|
||||
}
|
||||
WriteBatch batch;
|
||||
WriteBatchInternal::SetContents(&batch, record);
|
||||
printf("sequence %llu\n",
|
||||
static_cast<unsigned long long>(WriteBatchInternal::Sequence(&batch)));
|
||||
WriteBatchItemPrinter batch_item_printer;
|
||||
Status s = batch.Iterate(&batch_item_printer);
|
||||
if (!s.ok()) {
|
||||
printf(" error: %s\n", s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
bool DumpLog(Env* env, const std::string& fname) {
|
||||
return PrintLogContents(env, fname, WriteBatchPrinter);
|
||||
}
|
||||
|
||||
// Called on every log record (each one of which is a WriteBatch)
|
||||
// found in a kDescriptorFile.
|
||||
static void VersionEditPrinter(Slice record) {
|
||||
VersionEdit edit;
|
||||
Status s = edit.DecodeFrom(record);
|
||||
if (!s.ok()) {
|
||||
printf("%s\n", s.ToString().c_str());
|
||||
return;
|
||||
}
|
||||
printf("%s", edit.DebugString().c_str());
|
||||
}
|
||||
|
||||
bool DumpDescriptor(Env* env, const std::string& fname) {
|
||||
return PrintLogContents(env, fname, VersionEditPrinter);
|
||||
}
|
||||
|
||||
bool DumpTable(Env* env, const std::string& fname) {
|
||||
uint64_t file_size;
|
||||
RandomAccessFile* file = NULL;
|
||||
Table* table = NULL;
|
||||
Status s = env->GetFileSize(fname, &file_size);
|
||||
if (s.ok()) {
|
||||
s = env->NewRandomAccessFile(fname, &file);
|
||||
}
|
||||
if (s.ok()) {
|
||||
// We use the default comparator, which may or may not match the
|
||||
// comparator used in this database. However this should not cause
|
||||
// problems since we only use Table operations that do not require
|
||||
// any comparisons. In particular, we do not call Seek or Prev.
|
||||
s = Table::Open(Options(), file, file_size, &table);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "%s\n", s.ToString().c_str());
|
||||
delete table;
|
||||
delete file;
|
||||
return false;
|
||||
}
|
||||
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = false;
|
||||
Iterator* iter = table->NewIterator(ro);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ParsedInternalKey key;
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
printf("badkey '%s' => '%s'\n",
|
||||
EscapeString(iter->key()).c_str(),
|
||||
EscapeString(iter->value()).c_str());
|
||||
} else {
|
||||
char kbuf[20];
|
||||
const char* type;
|
||||
if (key.type == kTypeDeletion) {
|
||||
type = "del";
|
||||
} else if (key.type == kTypeValue) {
|
||||
type = "val";
|
||||
} else {
|
||||
snprintf(kbuf, sizeof(kbuf), "%d", static_cast<int>(key.type));
|
||||
type = kbuf;
|
||||
}
|
||||
printf("'%s' @ %8llu : %s => '%s'\n",
|
||||
EscapeString(key.user_key).c_str(),
|
||||
static_cast<unsigned long long>(key.sequence),
|
||||
type,
|
||||
EscapeString(iter->value()).c_str());
|
||||
}
|
||||
}
|
||||
s = iter->status();
|
||||
if (!s.ok()) {
|
||||
printf("iterator error: %s\n", s.ToString().c_str());
|
||||
}
|
||||
|
||||
delete iter;
|
||||
delete table;
|
||||
delete file;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DumpFile(Env* env, const std::string& fname) {
|
||||
FileType ftype;
|
||||
if (!GuessType(fname, &ftype)) {
|
||||
fprintf(stderr, "%s: unknown file type\n", fname.c_str());
|
||||
return false;
|
||||
}
|
||||
switch (ftype) {
|
||||
case kLogFile: return DumpLog(env, fname);
|
||||
case kDescriptorFile: return DumpDescriptor(env, fname);
|
||||
case kTableFile: return DumpTable(env, fname);
|
||||
|
||||
default: {
|
||||
fprintf(stderr, "%s: not a dump-able file type\n", fname.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HandleDumpCommand(Env* env, char** files, int num) {
|
||||
bool ok = true;
|
||||
for (int i = 0; i < num; i++) {
|
||||
ok &= DumpFile(env, files[i]);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace leveldb
|
||||
|
||||
static void Usage() {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Usage: leveldbutil command...\n"
|
||||
" dump files... -- dump contents of specified files\n"
|
||||
);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
leveldb::Env* env = leveldb::Env::Default();
|
||||
bool ok = true;
|
||||
if (argc < 2) {
|
||||
Usage();
|
||||
ok = false;
|
||||
} else {
|
||||
std::string command = argv[1];
|
||||
if (command == "dump") {
|
||||
ok = leveldb::HandleDumpCommand(env, argv+2, argc-2);
|
||||
} else {
|
||||
Usage();
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return (ok ? 0 : 1);
|
||||
}
|
||||
@@ -133,7 +133,9 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch) {
|
||||
|
||||
case kEof:
|
||||
if (in_fragmented_record) {
|
||||
ReportCorruption(scratch->size(), "partial record without end(3)");
|
||||
// This can be caused by the writer dying immediately after
|
||||
// writing a physical record but before completing the next; don't
|
||||
// treat it as a corruption, just ignore the entire logical record.
|
||||
scratch->clear();
|
||||
}
|
||||
return false;
|
||||
@@ -193,13 +195,12 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result) {
|
||||
eof_ = true;
|
||||
}
|
||||
continue;
|
||||
} else if (buffer_.size() == 0) {
|
||||
// End of file
|
||||
return kEof;
|
||||
} else {
|
||||
size_t drop_size = buffer_.size();
|
||||
// Note that if buffer_ is non-empty, we have a truncated header at the
|
||||
// end of the file, which can be caused by the writer crashing in the
|
||||
// middle of writing the header. Instead of considering this an error,
|
||||
// just report EOF.
|
||||
buffer_.clear();
|
||||
ReportCorruption(drop_size, "truncated record at end of file");
|
||||
return kEof;
|
||||
}
|
||||
}
|
||||
@@ -213,8 +214,14 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result) {
|
||||
if (kHeaderSize + length > buffer_.size()) {
|
||||
size_t drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
ReportCorruption(drop_size, "bad record length");
|
||||
return kBadRecord;
|
||||
if (!eof_) {
|
||||
ReportCorruption(drop_size, "bad record length");
|
||||
return kBadRecord;
|
||||
}
|
||||
// If the end of the file has been reached without reading |length| bytes
|
||||
// of payload, assume the writer died in the middle of writing the record.
|
||||
// Don't report a corruption.
|
||||
return kEof;
|
||||
}
|
||||
|
||||
if (type == kZeroType && length == 0) {
|
||||
|
||||
@@ -351,20 +351,32 @@ TEST(LogTest, BadRecordType) {
|
||||
ASSERT_EQ("OK", MatchError("unknown record type"));
|
||||
}
|
||||
|
||||
TEST(LogTest, TruncatedTrailingRecord) {
|
||||
TEST(LogTest, TruncatedTrailingRecordIsIgnored) {
|
||||
Write("foo");
|
||||
ShrinkSize(4); // Drop all payload as well as a header byte
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(kHeaderSize - 1, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("truncated record at end of file"));
|
||||
// Truncated last record is ignored, not treated as an error.
|
||||
ASSERT_EQ(0, DroppedBytes());
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
}
|
||||
|
||||
TEST(LogTest, BadLength) {
|
||||
const int kPayloadSize = kBlockSize - kHeaderSize;
|
||||
Write(BigString("bar", kPayloadSize));
|
||||
Write("foo");
|
||||
// Least significant size byte is stored in header[4].
|
||||
IncrementByte(4, 1);
|
||||
ASSERT_EQ("foo", Read());
|
||||
ASSERT_EQ(kBlockSize, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("bad record length"));
|
||||
}
|
||||
|
||||
TEST(LogTest, BadLengthAtEndIsIgnored) {
|
||||
Write("foo");
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ(kHeaderSize + 2, DroppedBytes());
|
||||
ASSERT_EQ("OK", MatchError("bad record length"));
|
||||
ASSERT_EQ(0, DroppedBytes());
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
}
|
||||
|
||||
TEST(LogTest, ChecksumMismatch) {
|
||||
@@ -415,6 +427,24 @@ TEST(LogTest, UnexpectedFirstType) {
|
||||
ASSERT_EQ("OK", MatchError("partial record without end"));
|
||||
}
|
||||
|
||||
TEST(LogTest, MissingLastIsIgnored) {
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Remove the LAST block, including header.
|
||||
ShrinkSize(14);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
ASSERT_EQ(0, DroppedBytes());
|
||||
}
|
||||
|
||||
TEST(LogTest, PartialLastIsIgnored) {
|
||||
Write(BigString("bar", kBlockSize));
|
||||
// Cause a bad record length in the LAST block.
|
||||
ShrinkSize(1);
|
||||
ASSERT_EQ("EOF", Read());
|
||||
ASSERT_EQ("", ReportMessage());
|
||||
ASSERT_EQ(0, DroppedBytes());
|
||||
}
|
||||
|
||||
TEST(LogTest, ErrorJoinsRecords) {
|
||||
// Consider two fragmented records:
|
||||
// first(R1) last(R1) first(R2) last(R2)
|
||||
|
||||
164
db/repair.cc
164
db/repair.cc
@@ -242,62 +242,134 @@ class Repairer {
|
||||
}
|
||||
|
||||
void ExtractMetaData() {
|
||||
std::vector<TableInfo> kept;
|
||||
for (size_t i = 0; i < table_numbers_.size(); i++) {
|
||||
TableInfo t;
|
||||
t.meta.number = table_numbers_[i];
|
||||
Status status = ScanTable(&t);
|
||||
if (!status.ok()) {
|
||||
std::string fname = TableFileName(dbname_, table_numbers_[i]);
|
||||
Log(options_.info_log, "Table #%llu: ignoring %s",
|
||||
(unsigned long long) table_numbers_[i],
|
||||
status.ToString().c_str());
|
||||
ArchiveFile(fname);
|
||||
} else {
|
||||
tables_.push_back(t);
|
||||
}
|
||||
ScanTable(table_numbers_[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Status ScanTable(TableInfo* t) {
|
||||
std::string fname = TableFileName(dbname_, t->meta.number);
|
||||
int counter = 0;
|
||||
Status status = env_->GetFileSize(fname, &t->meta.file_size);
|
||||
if (status.ok()) {
|
||||
Iterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), t->meta.number, t->meta.file_size);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t->max_sequence = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Slice key = iter->key();
|
||||
if (!ParseInternalKey(key, &parsed)) {
|
||||
Log(options_.info_log, "Table #%llu: unparsable key %s",
|
||||
(unsigned long long) t->meta.number,
|
||||
EscapeString(key).c_str());
|
||||
continue;
|
||||
}
|
||||
Iterator* NewTableIterator(const FileMetaData& meta) {
|
||||
// Same as compaction iterators: if paranoid_checks are on, turn
|
||||
// on checksum verification.
|
||||
ReadOptions r;
|
||||
r.verify_checksums = options_.paranoid_checks;
|
||||
return table_cache_->NewIterator(r, meta.number, meta.file_size);
|
||||
}
|
||||
|
||||
counter++;
|
||||
if (empty) {
|
||||
empty = false;
|
||||
t->meta.smallest.DecodeFrom(key);
|
||||
}
|
||||
t->meta.largest.DecodeFrom(key);
|
||||
if (parsed.sequence > t->max_sequence) {
|
||||
t->max_sequence = parsed.sequence;
|
||||
}
|
||||
void ScanTable(uint64_t number) {
|
||||
TableInfo t;
|
||||
t.meta.number = number;
|
||||
std::string fname = TableFileName(dbname_, number);
|
||||
Status status = env_->GetFileSize(fname, &t.meta.file_size);
|
||||
if (!status.ok()) {
|
||||
// Try alternate file name.
|
||||
fname = SSTTableFileName(dbname_, number);
|
||||
Status s2 = env_->GetFileSize(fname, &t.meta.file_size);
|
||||
if (s2.ok()) {
|
||||
status = Status::OK();
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
status = iter->status();
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
if (!status.ok()) {
|
||||
ArchiveFile(TableFileName(dbname_, number));
|
||||
ArchiveFile(SSTTableFileName(dbname_, number));
|
||||
Log(options_.info_log, "Table #%llu: dropped: %s",
|
||||
(unsigned long long) t.meta.number,
|
||||
status.ToString().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract metadata by scanning through table.
|
||||
int counter = 0;
|
||||
Iterator* iter = NewTableIterator(t.meta);
|
||||
bool empty = true;
|
||||
ParsedInternalKey parsed;
|
||||
t.max_sequence = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Slice key = iter->key();
|
||||
if (!ParseInternalKey(key, &parsed)) {
|
||||
Log(options_.info_log, "Table #%llu: unparsable key %s",
|
||||
(unsigned long long) t.meta.number,
|
||||
EscapeString(key).c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
counter++;
|
||||
if (empty) {
|
||||
empty = false;
|
||||
t.meta.smallest.DecodeFrom(key);
|
||||
}
|
||||
t.meta.largest.DecodeFrom(key);
|
||||
if (parsed.sequence > t.max_sequence) {
|
||||
t.max_sequence = parsed.sequence;
|
||||
}
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
status = iter->status();
|
||||
}
|
||||
delete iter;
|
||||
Log(options_.info_log, "Table #%llu: %d entries %s",
|
||||
(unsigned long long) t->meta.number,
|
||||
(unsigned long long) t.meta.number,
|
||||
counter,
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
|
||||
if (status.ok()) {
|
||||
tables_.push_back(t);
|
||||
} else {
|
||||
RepairTable(fname, t); // RepairTable archives input file.
|
||||
}
|
||||
}
|
||||
|
||||
void RepairTable(const std::string& src, TableInfo t) {
|
||||
// We will copy src contents to a new table and then rename the
|
||||
// new table over the source.
|
||||
|
||||
// Create builder.
|
||||
std::string copy = TableFileName(dbname_, next_file_number_++);
|
||||
WritableFile* file;
|
||||
Status s = env_->NewWritableFile(copy, &file);
|
||||
if (!s.ok()) {
|
||||
return;
|
||||
}
|
||||
TableBuilder* builder = new TableBuilder(options_, file);
|
||||
|
||||
// Copy data.
|
||||
Iterator* iter = NewTableIterator(t.meta);
|
||||
int counter = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
builder->Add(iter->key(), iter->value());
|
||||
counter++;
|
||||
}
|
||||
delete iter;
|
||||
|
||||
ArchiveFile(src);
|
||||
if (counter == 0) {
|
||||
builder->Abandon(); // Nothing to save
|
||||
} else {
|
||||
s = builder->Finish();
|
||||
if (s.ok()) {
|
||||
t.meta.file_size = builder->FileSize();
|
||||
}
|
||||
}
|
||||
delete builder;
|
||||
builder = NULL;
|
||||
|
||||
if (s.ok()) {
|
||||
s = file->Close();
|
||||
}
|
||||
delete file;
|
||||
file = NULL;
|
||||
|
||||
if (counter > 0 && s.ok()) {
|
||||
std::string orig = TableFileName(dbname_, t.meta.number);
|
||||
s = env_->RenameFile(copy, orig);
|
||||
if (s.ok()) {
|
||||
Log(options_.info_log, "Table #%llu: %d entries repaired",
|
||||
(unsigned long long) t.meta.number, counter);
|
||||
tables_.push_back(t);
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
env_->DeleteFile(copy);
|
||||
}
|
||||
}
|
||||
|
||||
Status WriteDescriptor() {
|
||||
|
||||
@@ -54,6 +54,12 @@ Status TableCache::FindTable(uint64_t file_number, uint64_t file_size,
|
||||
RandomAccessFile* file = NULL;
|
||||
Table* table = NULL;
|
||||
s = env_->NewRandomAccessFile(fname, &file);
|
||||
if (!s.ok()) {
|
||||
std::string old_fname = SSTTableFileName(dbname_, file_number);
|
||||
if (env_->NewRandomAccessFile(old_fname, &file).ok()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = Table::Open(*options_, file, file_size, &table);
|
||||
}
|
||||
|
||||
@@ -54,20 +54,6 @@ static int64_t TotalFileSize(const std::vector<FileMetaData*>& files) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::string IntSetToString(const std::set<uint64_t>& s) {
|
||||
std::string result = "{";
|
||||
for (std::set<uint64_t>::const_iterator it = s.begin();
|
||||
it != s.end();
|
||||
++it) {
|
||||
result += (result.size() > 1) ? "," : "";
|
||||
result += NumberToString(*it);
|
||||
}
|
||||
result += "}";
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Version::~Version() {
|
||||
assert(refs_ == 0);
|
||||
|
||||
@@ -132,7 +118,7 @@ bool SomeFileOverlapsRange(
|
||||
const Comparator* ucmp = icmp.user_comparator();
|
||||
if (!disjoint_sorted_files) {
|
||||
// Need to check against all files
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
const FileMetaData* f = files[i];
|
||||
if (AfterFile(ucmp, smallest_user_key, f) ||
|
||||
BeforeFile(ucmp, largest_user_key, f)) {
|
||||
@@ -289,6 +275,51 @@ static bool NewestFirst(FileMetaData* a, FileMetaData* b) {
|
||||
return a->number > b->number;
|
||||
}
|
||||
|
||||
void Version::ForEachOverlapping(Slice user_key, Slice internal_key,
|
||||
void* arg,
|
||||
bool (*func)(void*, int, FileMetaData*)) {
|
||||
// TODO(sanjay): Change Version::Get() to use this function.
|
||||
const Comparator* ucmp = vset_->icmp_.user_comparator();
|
||||
|
||||
// Search level-0 in order from newest to oldest.
|
||||
std::vector<FileMetaData*> tmp;
|
||||
tmp.reserve(files_[0].size());
|
||||
for (uint32_t i = 0; i < files_[0].size(); i++) {
|
||||
FileMetaData* f = files_[0][i];
|
||||
if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 &&
|
||||
ucmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
tmp.push_back(f);
|
||||
}
|
||||
}
|
||||
if (!tmp.empty()) {
|
||||
std::sort(tmp.begin(), tmp.end(), NewestFirst);
|
||||
for (uint32_t i = 0; i < tmp.size(); i++) {
|
||||
if (!(*func)(arg, 0, tmp[i])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search other levels.
|
||||
for (int level = 1; level < config::kNumLevels; level++) {
|
||||
size_t num_files = files_[level].size();
|
||||
if (num_files == 0) continue;
|
||||
|
||||
// Binary search to find earliest index whose largest key >= internal_key.
|
||||
uint32_t index = FindFile(vset_->icmp_, files_[level], internal_key);
|
||||
if (index < num_files) {
|
||||
FileMetaData* f = files_[level][index];
|
||||
if (ucmp->Compare(user_key, f->smallest.user_key()) < 0) {
|
||||
// All of "f" is past any data for user_key
|
||||
} else {
|
||||
if (!(*func)(arg, level, f)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status Version::Get(const ReadOptions& options,
|
||||
const LookupKey& k,
|
||||
std::string* value,
|
||||
@@ -401,6 +432,44 @@ bool Version::UpdateStats(const GetStats& stats) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Version::RecordReadSample(Slice internal_key) {
|
||||
ParsedInternalKey ikey;
|
||||
if (!ParseInternalKey(internal_key, &ikey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct State {
|
||||
GetStats stats; // Holds first matching file
|
||||
int matches;
|
||||
|
||||
static bool Match(void* arg, int level, FileMetaData* f) {
|
||||
State* state = reinterpret_cast<State*>(arg);
|
||||
state->matches++;
|
||||
if (state->matches == 1) {
|
||||
// Remember first match.
|
||||
state->stats.seek_file = f;
|
||||
state->stats.seek_file_level = level;
|
||||
}
|
||||
// We can stop iterating once we have a second match.
|
||||
return state->matches < 2;
|
||||
}
|
||||
};
|
||||
|
||||
State state;
|
||||
state.matches = 0;
|
||||
ForEachOverlapping(ikey.user_key, internal_key, &state, &State::Match);
|
||||
|
||||
// Must have at least two matches since we want to merge across
|
||||
// files. But what if we have a single file that contains many
|
||||
// overwrites and deletions? Should we have another mechanism for
|
||||
// finding such files?
|
||||
if (state.matches >= 2) {
|
||||
// 1MB cost is about 1 seek (see comment in Builder::Apply).
|
||||
return UpdateStats(state.stats);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Version::Ref() {
|
||||
++refs_;
|
||||
}
|
||||
@@ -435,10 +504,13 @@ int Version::PickLevelForMemTableOutput(
|
||||
if (OverlapInLevel(level + 1, &smallest_user_key, &largest_user_key)) {
|
||||
break;
|
||||
}
|
||||
GetOverlappingInputs(level + 2, &start, &limit, &overlaps);
|
||||
const int64_t sum = TotalFileSize(overlaps);
|
||||
if (sum > kMaxGrandParentOverlapBytes) {
|
||||
break;
|
||||
if (level + 2 < config::kNumLevels) {
|
||||
// Check that file does not overlap too many grandparent bytes.
|
||||
GetOverlappingInputs(level + 2, &start, &limit, &overlaps);
|
||||
const int64_t sum = TotalFileSize(overlaps);
|
||||
if (sum > kMaxGrandParentOverlapBytes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
level++;
|
||||
}
|
||||
@@ -452,6 +524,8 @@ void Version::GetOverlappingInputs(
|
||||
const InternalKey* begin,
|
||||
const InternalKey* end,
|
||||
std::vector<FileMetaData*>* inputs) {
|
||||
assert(level >= 0);
|
||||
assert(level < config::kNumLevels);
|
||||
inputs->clear();
|
||||
Slice user_begin, user_end;
|
||||
if (begin != NULL) {
|
||||
@@ -786,6 +860,9 @@ Status VersionSet::LogAndApply(VersionEdit* edit, port::Mutex* mu) {
|
||||
if (s.ok()) {
|
||||
s = descriptor_file_->Sync();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
Log(options_->info_log, "MANIFEST write: %s\n", s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// If we just created a new descriptor file, install it by writing a
|
||||
@@ -865,7 +942,7 @@ Status VersionSet::Recover() {
|
||||
if (edit.has_comparator_ &&
|
||||
edit.comparator_ != icmp_.user_comparator()->Name()) {
|
||||
s = Status::InvalidArgument(
|
||||
edit.comparator_ + "does not match existing comparator ",
|
||||
edit.comparator_ + " does not match existing comparator ",
|
||||
icmp_.user_comparator()->Name());
|
||||
}
|
||||
}
|
||||
@@ -1295,14 +1372,19 @@ Compaction* VersionSet::CompactRange(
|
||||
}
|
||||
|
||||
// Avoid compacting too much in one shot in case the range is large.
|
||||
const uint64_t limit = MaxFileSizeForLevel(level);
|
||||
uint64_t total = 0;
|
||||
for (int i = 0; i < inputs.size(); i++) {
|
||||
uint64_t s = inputs[i]->file_size;
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
inputs.resize(i + 1);
|
||||
break;
|
||||
// But we cannot do this for level-0 since level-0 files can overlap
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if (level > 0) {
|
||||
const uint64_t limit = MaxFileSizeForLevel(level);
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i < inputs.size(); i++) {
|
||||
uint64_t s = inputs[i]->file_size;
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
inputs.resize(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -77,6 +78,12 @@ class Version {
|
||||
// REQUIRES: lock is held
|
||||
bool UpdateStats(const GetStats& stats);
|
||||
|
||||
// Record a sample of bytes read at the specified internal key.
|
||||
// Samples are taken approximately once every config::kReadBytesPeriod
|
||||
// bytes. Returns true if a new compaction may need to be triggered.
|
||||
// REQUIRES: lock is held
|
||||
bool RecordReadSample(Slice key);
|
||||
|
||||
// Reference count management (so Versions do not disappear out from
|
||||
// under live iterators)
|
||||
void Ref();
|
||||
@@ -113,6 +120,15 @@ class Version {
|
||||
class LevelFileNumIterator;
|
||||
Iterator* NewConcatenatingIterator(const ReadOptions&, int level) const;
|
||||
|
||||
// Call func(arg, level, f) for every file that overlaps user_key in
|
||||
// order from newest to oldest. If an invocation of func returns
|
||||
// false, makes no more calls.
|
||||
//
|
||||
// REQUIRES: user portion of internal_key == user_key.
|
||||
void ForEachOverlapping(Slice user_key, Slice internal_key,
|
||||
void* arg,
|
||||
bool (*func)(void*, int, FileMetaData*));
|
||||
|
||||
VersionSet* vset_; // VersionSet to which this Version belongs
|
||||
Version* next_; // Next version in linked list
|
||||
Version* prev_; // Previous version in linked list
|
||||
@@ -159,7 +175,8 @@ class VersionSet {
|
||||
// current version. Will release *mu while actually writing to the file.
|
||||
// REQUIRES: *mu is held on entry.
|
||||
// REQUIRES: no other thread concurrently calls LogAndApply()
|
||||
Status LogAndApply(VersionEdit* edit, port::Mutex* mu);
|
||||
Status LogAndApply(VersionEdit* edit, port::Mutex* mu)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(mu);
|
||||
|
||||
// Recover the last saved descriptor from persistent storage.
|
||||
Status Recover();
|
||||
@@ -173,6 +190,15 @@ class VersionSet {
|
||||
// Allocate and return a new file number
|
||||
uint64_t NewFileNumber() { return next_file_number_++; }
|
||||
|
||||
// Arrange to reuse "file_number" unless a newer file number has
|
||||
// already been allocated.
|
||||
// REQUIRES: "file_number" was returned by a call to NewFileNumber().
|
||||
void ReuseFileNumber(uint64_t file_number) {
|
||||
if (next_file_number_ == file_number + 1) {
|
||||
next_file_number_ = file_number;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the number of Table files at the specified level.
|
||||
int NumLevelFiles(int level) const;
|
||||
|
||||
|
||||
@@ -75,6 +75,9 @@ static bool FLAGS_transaction = true;
|
||||
// If true, we enable Write-Ahead Logging
|
||||
static bool FLAGS_WAL_enabled = true;
|
||||
|
||||
// Use the db with the following name.
|
||||
static const char* FLAGS_db = NULL;
|
||||
|
||||
inline
|
||||
static void ExecErrorCheck(int status, char *err_msg) {
|
||||
if (status != SQLITE_OK) {
|
||||
@@ -317,11 +320,16 @@ class Benchmark {
|
||||
bytes_(0),
|
||||
rand_(301) {
|
||||
std::vector<std::string> files;
|
||||
Env::Default()->GetChildren("/tmp", &files);
|
||||
std::string test_dir;
|
||||
Env::Default()->GetTestDirectory(&test_dir);
|
||||
Env::Default()->GetChildren(test_dir, &files);
|
||||
if (!FLAGS_use_existing_db) {
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
if (Slice(files[i]).starts_with("dbbench_sqlite3")) {
|
||||
Env::Default()->DeleteFile("/tmp/" + files[i]);
|
||||
std::string file_name(test_dir);
|
||||
file_name += "/";
|
||||
file_name += files[i];
|
||||
Env::Default()->DeleteFile(file_name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -415,7 +423,11 @@ class Benchmark {
|
||||
db_num_++;
|
||||
|
||||
// Open database
|
||||
snprintf(file_name, sizeof(file_name), "/tmp/dbbench_sqlite3-%d.db",
|
||||
std::string tmp_dir;
|
||||
Env::Default()->GetTestDirectory(&tmp_dir);
|
||||
snprintf(file_name, sizeof(file_name),
|
||||
"%s/dbbench_sqlite3-%d.db",
|
||||
tmp_dir.c_str(),
|
||||
db_num_);
|
||||
status = sqlite3_open(file_name, &db_);
|
||||
if (status) {
|
||||
@@ -606,7 +618,7 @@ class Benchmark {
|
||||
ErrorCheck(status);
|
||||
|
||||
// Execute read statement
|
||||
while ((status = sqlite3_step(read_stmt)) == SQLITE_ROW);
|
||||
while ((status = sqlite3_step(read_stmt)) == SQLITE_ROW) {}
|
||||
StepErrorCheck(status);
|
||||
|
||||
// Reset SQLite statement for another use
|
||||
@@ -655,6 +667,7 @@ class Benchmark {
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::string default_db_path;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
double d;
|
||||
int n;
|
||||
@@ -684,12 +697,21 @@ int main(int argc, char** argv) {
|
||||
} else if (sscanf(argv[i], "--WAL_enabled=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_WAL_enabled = n;
|
||||
} else if (strncmp(argv[i], "--db=", 5) == 0) {
|
||||
FLAGS_db = argv[i] + 5;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Choose a location for the test database if none given with --db=<path>
|
||||
if (FLAGS_db == NULL) {
|
||||
leveldb::Env::Default()->GetTestDirectory(&default_db_path);
|
||||
default_db_path += "/dbbench";
|
||||
FLAGS_db = default_db_path.c_str();
|
||||
}
|
||||
|
||||
leveldb::Benchmark benchmark;
|
||||
benchmark.Run();
|
||||
return 0;
|
||||
|
||||
@@ -68,6 +68,9 @@ static bool FLAGS_use_existing_db = false;
|
||||
// is off.
|
||||
static bool FLAGS_compression = true;
|
||||
|
||||
// Use the db with the following name.
|
||||
static const char* FLAGS_db = NULL;
|
||||
|
||||
inline
|
||||
static void DBSynchronize(kyotocabinet::TreeDB* db_)
|
||||
{
|
||||
@@ -292,11 +295,16 @@ class Benchmark {
|
||||
bytes_(0),
|
||||
rand_(301) {
|
||||
std::vector<std::string> files;
|
||||
Env::Default()->GetChildren("/tmp", &files);
|
||||
std::string test_dir;
|
||||
Env::Default()->GetTestDirectory(&test_dir);
|
||||
Env::Default()->GetChildren(test_dir.c_str(), &files);
|
||||
if (!FLAGS_use_existing_db) {
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
if (Slice(files[i]).starts_with("dbbench_polyDB")) {
|
||||
Env::Default()->DeleteFile("/tmp/" + files[i]);
|
||||
std::string file_name(test_dir);
|
||||
file_name += "/";
|
||||
file_name += files[i];
|
||||
Env::Default()->DeleteFile(file_name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,8 +393,12 @@ class Benchmark {
|
||||
db_ = new kyotocabinet::TreeDB();
|
||||
char file_name[100];
|
||||
db_num_++;
|
||||
snprintf(file_name, sizeof(file_name), "/tmp/dbbench_polyDB-%d.kct",
|
||||
db_num_);
|
||||
std::string test_dir;
|
||||
Env::Default()->GetTestDirectory(&test_dir);
|
||||
snprintf(file_name, sizeof(file_name),
|
||||
"%s/dbbench_polyDB-%d.kct",
|
||||
test_dir.c_str(),
|
||||
db_num_);
|
||||
|
||||
// Create tuning options and open the database
|
||||
int open_options = kyotocabinet::PolyDB::OWRITER |
|
||||
@@ -470,6 +482,7 @@ class Benchmark {
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::string default_db_path;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
double d;
|
||||
int n;
|
||||
@@ -494,12 +507,21 @@ int main(int argc, char** argv) {
|
||||
} else if (sscanf(argv[i], "--compression=%d%c", &n, &junk) == 1 &&
|
||||
(n == 0 || n == 1)) {
|
||||
FLAGS_compression = (n == 1) ? true : false;
|
||||
} else if (strncmp(argv[i], "--db=", 5) == 0) {
|
||||
FLAGS_db = argv[i] + 5;
|
||||
} else {
|
||||
fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Choose a location for the test database if none given with --db=<path>
|
||||
if (FLAGS_db == NULL) {
|
||||
leveldb::Env::Default()->GetTestDirectory(&default_db_path);
|
||||
default_db_path += "/dbbench";
|
||||
FLAGS_db = default_db_path.c_str();
|
||||
}
|
||||
|
||||
leveldb::Benchmark benchmark;
|
||||
benchmark.Run();
|
||||
return 0;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
The implementation of leveldb is similar in spirit to the
|
||||
representation of a single
|
||||
<a href="http://labs.google.com/papers/bigtable.html">
|
||||
<a href="http://research.google.com/archive/bigtable.html">
|
||||
Bigtable tablet (section 5.3)</a>.
|
||||
However the organization of the files that make up the representation
|
||||
is somewhat different and is explained below.
|
||||
|
||||
@@ -408,7 +408,7 @@ The optional <code>FilterPolicy</code> mechanism can be used to reduce
|
||||
the number of disk reads substantially.
|
||||
<pre>
|
||||
leveldb::Options options;
|
||||
options.filter_policy = NewBloomFilter(10);
|
||||
options.filter_policy = NewBloomFilterPolicy(10);
|
||||
leveldb::DB* db;
|
||||
leveldb::DB::Open(options, "/tmp/testdb", &db);
|
||||
... use the database ...
|
||||
@@ -420,7 +420,7 @@ The preceding code associates a
|
||||
based filtering policy with the database. Bloom filter based
|
||||
filtering relies on keeping some number of bits of data in memory per
|
||||
key (in this case 10 bits per key since that is the argument we passed
|
||||
to NewBloomFilter). This filter will reduce the number of unnecessary
|
||||
to NewBloomFilterPolicy). This filter will reduce the number of unnecessary
|
||||
disk reads needed for <code>Get()</code> calls by a factor of
|
||||
approximately a 100. Increasing the bits per key will lead to a
|
||||
larger reduction at the cost of more memory usage. We recommend that
|
||||
@@ -430,7 +430,7 @@ lot of random reads set a filter policy.
|
||||
If you are using a custom comparator, you should ensure that the filter
|
||||
policy you are using is compatible with your comparator. For example,
|
||||
consider a comparator that ignores trailing spaces when comparing keys.
|
||||
<code>NewBloomFilter</code> must not be used with such a comparator.
|
||||
<code>NewBloomFilterPolicy</code> must not be used with such a comparator.
|
||||
Instead, the application should provide a custom filter policy that
|
||||
also ignores trailing spaces. For example:
|
||||
<pre>
|
||||
@@ -438,7 +438,7 @@ also ignores trailing spaces. For example:
|
||||
private:
|
||||
FilterPolicy* builtin_policy_;
|
||||
public:
|
||||
CustomFilterPolicy() : builtin_policy_(NewBloomFilter(10)) { }
|
||||
CustomFilterPolicy() : builtin_policy_(NewBloomFilterPolicy(10)) { }
|
||||
~CustomFilterPolicy() { delete builtin_policy_; }
|
||||
|
||||
const char* Name() const { return "IgnoreTrailingSpacesFilter"; }
|
||||
|
||||
@@ -4,8 +4,8 @@ exception is that the tail of the file may contain a partial block.
|
||||
Each block consists of a sequence of records:
|
||||
block := record* trailer?
|
||||
record :=
|
||||
checksum: uint32 // crc32c of type and data[]
|
||||
length: uint16
|
||||
checksum: uint32 // crc32c of type and data[] ; little-endian
|
||||
length: uint16 // little-endian
|
||||
type: uint8 // One of FULL, FIRST, MIDDLE, LAST
|
||||
data: uint8[length]
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ The file contains internal pointers. Each such pointer is called
|
||||
a BlockHandle and contains the following information:
|
||||
offset: varint64
|
||||
size: varint64
|
||||
See https://developers.google.com/protocol-buffers/docs/encoding#varints
|
||||
for an explanation of varint64 format.
|
||||
|
||||
(1) The sequence of key/value pairs in the file are stored in sorted
|
||||
order and partitioned into a sequence of data blocks. These blocks
|
||||
@@ -41,11 +43,11 @@ BlockHandle for the data block.
|
||||
|
||||
(6) At the very end of the file is a fixed length footer that contains
|
||||
the BlockHandle of the metaindex and index blocks as well as a magic number.
|
||||
metaindex_handle: char[p]; // Block handle for metaindex
|
||||
index_handle: char[q]; // Block handle for index
|
||||
padding: char[40-p-q]; // 0 bytes to make fixed length
|
||||
// (40==2*BlockHandle::kMaxEncodedLength)
|
||||
magic: fixed64; // == 0xdb4775248b80fb57
|
||||
metaindex_handle: char[p]; // Block handle for metaindex
|
||||
index_handle: char[q]; // Block handle for index
|
||||
padding: char[40-p-q]; // zeroed bytes to make fixed length
|
||||
// (40==2*BlockHandle::kMaxEncodedLength)
|
||||
magic: fixed64; // == 0xdb4775248b80fb57 (little-endian)
|
||||
|
||||
"filter" Meta Block
|
||||
-------------------
|
||||
|
||||
@@ -221,6 +221,11 @@ class WritableFileImpl : public WritableFile {
|
||||
FileState* file_;
|
||||
};
|
||||
|
||||
class NoOpLogger : public Logger {
|
||||
public:
|
||||
virtual void Logv(const char* format, va_list ap) { }
|
||||
};
|
||||
|
||||
class InMemoryEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit InMemoryEnv(Env* base_env) : EnvWrapper(base_env) { }
|
||||
@@ -358,6 +363,11 @@ class InMemoryEnv : public EnvWrapper {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status NewLogger(const std::string& fname, Logger** result) {
|
||||
*result = new NoOpLogger;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
// Map from filenames to FileState objects, representing a simple file system.
|
||||
typedef std::map<std::string, FileState*> FileSystem;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
Does not support:
|
||||
. getters for the option types
|
||||
. custom comparators that implement key shortening
|
||||
. capturing post-write-snapshot
|
||||
. custom iter, db, env, cache implementations using just the C bindings
|
||||
|
||||
Some conventions:
|
||||
@@ -28,6 +27,7 @@
|
||||
be true on entry:
|
||||
*errptr == NULL
|
||||
*errptr points to a malloc()ed null-terminated error message
|
||||
(On Windows, *errptr must have been malloc()-ed by this library.)
|
||||
On success, a leveldb routine leaves *errptr unchanged.
|
||||
On failure, leveldb frees the old value of *errptr and
|
||||
set *errptr to a malloc()ed error message.
|
||||
@@ -268,6 +268,21 @@ extern void leveldb_cache_destroy(leveldb_cache_t* cache);
|
||||
extern leveldb_env_t* leveldb_create_default_env();
|
||||
extern void leveldb_env_destroy(leveldb_env_t*);
|
||||
|
||||
/* Utility */
|
||||
|
||||
/* Calls free(ptr).
|
||||
REQUIRES: ptr was malloc()-ed and returned by one of the routines
|
||||
in this file. Note that in certain cases (typically on Windows), you
|
||||
may need to call this routine instead of free(ptr) to dispose of
|
||||
malloc()-ed memory returned by this library. */
|
||||
extern void leveldb_free(void* ptr);
|
||||
|
||||
/* Return the major version number for this release. */
|
||||
extern int leveldb_major_version();
|
||||
|
||||
/* Return the minor version number for this release. */
|
||||
extern int leveldb_minor_version();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end extern "C" */
|
||||
#endif
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace leveldb {
|
||||
|
||||
// Update Makefile if you change these
|
||||
static const int kMajorVersion = 1;
|
||||
static const int kMinorVersion = 4;
|
||||
static const int kMinorVersion = 17;
|
||||
|
||||
struct Options;
|
||||
struct ReadOptions;
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
#ifndef STORAGE_LEVELDB_INCLUDE_ENV_H_
|
||||
#define STORAGE_LEVELDB_INCLUDE_ENV_H_
|
||||
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include "leveldb/status.h"
|
||||
|
||||
@@ -175,6 +175,11 @@ class SequentialFile {
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
virtual Status Skip(uint64_t n) = 0;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
SequentialFile(const SequentialFile&);
|
||||
void operator=(const SequentialFile&);
|
||||
};
|
||||
|
||||
// A file abstraction for randomly reading the contents of a file.
|
||||
@@ -194,6 +199,11 @@ class RandomAccessFile {
|
||||
// Safe for concurrent use by multiple threads.
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const = 0;
|
||||
|
||||
private:
|
||||
// No copying allowed
|
||||
RandomAccessFile(const RandomAccessFile&);
|
||||
void operator=(const RandomAccessFile&);
|
||||
};
|
||||
|
||||
// A file abstraction for sequential writing. The implementation
|
||||
|
||||
@@ -94,7 +94,7 @@ inline bool operator!=(const Slice& x, const Slice& y) {
|
||||
}
|
||||
|
||||
inline int Slice::compare(const Slice& b) const {
|
||||
const int min_len = (size_ < b.size_) ? size_ : b.size_;
|
||||
const size_t min_len = (size_ < b.size_) ? size_ : b.size_;
|
||||
int r = memcmp(data_, b.data_, min_len);
|
||||
if (r == 0) {
|
||||
if (size_ < b.size_) r = -1;
|
||||
|
||||
92
issues/issue178_test.cc
Normal file
92
issues/issue178_test.cc
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2013 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.
|
||||
|
||||
// Test for issue 178: a manual compaction causes deleted data to reappear.
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "leveldb/db.h"
|
||||
#include "leveldb/write_batch.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const int kNumKeys = 1100000;
|
||||
|
||||
std::string Key1(int i) {
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "my_key_%d", i);
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string Key2(int i) {
|
||||
return Key1(i) + "_xxx";
|
||||
}
|
||||
|
||||
class Issue178 { };
|
||||
|
||||
TEST(Issue178, Test) {
|
||||
// Get rid of any state from an old run.
|
||||
std::string dbpath = leveldb::test::TmpDir() + "/leveldb_cbug_test";
|
||||
DestroyDB(dbpath, leveldb::Options());
|
||||
|
||||
// Open database. Disable compression since it affects the creation
|
||||
// of layers and the code below is trying to test against a very
|
||||
// specific scenario.
|
||||
leveldb::DB* db;
|
||||
leveldb::Options db_options;
|
||||
db_options.create_if_missing = true;
|
||||
db_options.compression = leveldb::kNoCompression;
|
||||
ASSERT_OK(leveldb::DB::Open(db_options, dbpath, &db));
|
||||
|
||||
// create first key range
|
||||
leveldb::WriteBatch batch;
|
||||
for (size_t i = 0; i < kNumKeys; i++) {
|
||||
batch.Put(Key1(i), "value for range 1 key");
|
||||
}
|
||||
ASSERT_OK(db->Write(leveldb::WriteOptions(), &batch));
|
||||
|
||||
// create second key range
|
||||
batch.Clear();
|
||||
for (size_t i = 0; i < kNumKeys; i++) {
|
||||
batch.Put(Key2(i), "value for range 2 key");
|
||||
}
|
||||
ASSERT_OK(db->Write(leveldb::WriteOptions(), &batch));
|
||||
|
||||
// delete second key range
|
||||
batch.Clear();
|
||||
for (size_t i = 0; i < kNumKeys; i++) {
|
||||
batch.Delete(Key2(i));
|
||||
}
|
||||
ASSERT_OK(db->Write(leveldb::WriteOptions(), &batch));
|
||||
|
||||
// compact database
|
||||
std::string start_key = Key1(0);
|
||||
std::string end_key = Key1(kNumKeys - 1);
|
||||
leveldb::Slice least(start_key.data(), start_key.size());
|
||||
leveldb::Slice greatest(end_key.data(), end_key.size());
|
||||
|
||||
// commenting out the line below causes the example to work correctly
|
||||
db->CompactRange(&least, &greatest);
|
||||
|
||||
// count the keys
|
||||
leveldb::Iterator* iter = db->NewIterator(leveldb::ReadOptions());
|
||||
size_t num_keys = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
num_keys++;
|
||||
}
|
||||
delete iter;
|
||||
ASSERT_EQ(kNumKeys, num_keys) << "Bad number of keys";
|
||||
|
||||
// close database
|
||||
delete db;
|
||||
DestroyDB(dbpath, leveldb::Options());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return leveldb::test::RunAllTests();
|
||||
}
|
||||
59
issues/issue200_test.cc
Normal file
59
issues/issue200_test.cc
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2013 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.
|
||||
|
||||
// Test for issue 200: when iterator switches direction from backward
|
||||
// to forward, the current key can be yielded unexpectedly if a new
|
||||
// mutation has been added just before the current key.
|
||||
|
||||
#include "leveldb/db.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
class Issue200 { };
|
||||
|
||||
TEST(Issue200, Test) {
|
||||
// Get rid of any state from an old run.
|
||||
std::string dbpath = test::TmpDir() + "/leveldb_issue200_test";
|
||||
DestroyDB(dbpath, Options());
|
||||
|
||||
DB *db;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(DB::Open(options, dbpath, &db));
|
||||
|
||||
WriteOptions write_options;
|
||||
ASSERT_OK(db->Put(write_options, "1", "b"));
|
||||
ASSERT_OK(db->Put(write_options, "2", "c"));
|
||||
ASSERT_OK(db->Put(write_options, "3", "d"));
|
||||
ASSERT_OK(db->Put(write_options, "4", "e"));
|
||||
ASSERT_OK(db->Put(write_options, "5", "f"));
|
||||
|
||||
ReadOptions read_options;
|
||||
Iterator *iter = db->NewIterator(read_options);
|
||||
|
||||
// Add an element that should not be reflected in the iterator.
|
||||
ASSERT_OK(db->Put(write_options, "25", "cd"));
|
||||
|
||||
iter->Seek("5");
|
||||
ASSERT_EQ(iter->key().ToString(), "5");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(iter->key().ToString(), "4");
|
||||
iter->Prev();
|
||||
ASSERT_EQ(iter->key().ToString(), "3");
|
||||
iter->Next();
|
||||
ASSERT_EQ(iter->key().ToString(), "4");
|
||||
iter->Next();
|
||||
ASSERT_EQ(iter->key().ToString(), "5");
|
||||
|
||||
delete iter;
|
||||
delete db;
|
||||
DestroyDB(dbpath, options);
|
||||
}
|
||||
|
||||
} // namespace leveldb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
return leveldb::test::RunAllTests();
|
||||
}
|
||||
@@ -36,6 +36,8 @@
|
||||
#define ARCH_CPU_X86_FAMILY 1
|
||||
#elif defined(__ARMEL__)
|
||||
#define ARCH_CPU_ARM_FAMILY 1
|
||||
#elif defined(__ppc__) || defined(__powerpc__) || defined(__powerpc64__)
|
||||
#define ARCH_CPU_PPC_FAMILY 1
|
||||
#endif
|
||||
|
||||
namespace leveldb {
|
||||
@@ -48,6 +50,13 @@ namespace port {
|
||||
// http://msdn.microsoft.com/en-us/library/ms684208(v=vs.85).aspx
|
||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||
|
||||
// Mac OS
|
||||
#elif defined(OS_MACOSX)
|
||||
inline void MemoryBarrier() {
|
||||
OSMemoryBarrier();
|
||||
}
|
||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||
|
||||
// Gcc on x86
|
||||
#elif defined(ARCH_CPU_X86_FAMILY) && defined(__GNUC__)
|
||||
inline void MemoryBarrier() {
|
||||
@@ -66,20 +75,30 @@ inline void MemoryBarrier() {
|
||||
}
|
||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||
|
||||
// Mac OS
|
||||
#elif defined(OS_MACOSX)
|
||||
// ARM Linux
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY) && defined(__linux__)
|
||||
typedef void (*LinuxKernelMemoryBarrierFunc)(void);
|
||||
// The Linux ARM kernel provides a highly optimized device-specific memory
|
||||
// barrier function at a fixed memory address that is mapped in every
|
||||
// user-level process.
|
||||
//
|
||||
// This beats using CPU-specific instructions which are, on single-core
|
||||
// devices, un-necessary and very costly (e.g. ARMv7-A "dmb" takes more
|
||||
// than 180ns on a Cortex-A8 like the one on a Nexus One). Benchmarking
|
||||
// shows that the extra function call cost is completely negligible on
|
||||
// multi-core devices.
|
||||
//
|
||||
inline void MemoryBarrier() {
|
||||
OSMemoryBarrier();
|
||||
(*(LinuxKernelMemoryBarrierFunc)0xffff0fa0)();
|
||||
}
|
||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||
|
||||
// ARM
|
||||
#elif defined(ARCH_CPU_ARM_FAMILY)
|
||||
typedef void (*LinuxKernelMemoryBarrierFunc)(void);
|
||||
LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier __attribute__((weak)) =
|
||||
(LinuxKernelMemoryBarrierFunc) 0xffff0fa0;
|
||||
// PPC
|
||||
#elif defined(ARCH_CPU_PPC_FAMILY) && defined(__GNUC__)
|
||||
inline void MemoryBarrier() {
|
||||
pLinuxKernelMemoryBarrier();
|
||||
// TODO for some powerpc expert: is there a cheaper suitable variant?
|
||||
// Perhaps by having separate barriers for acquire and release ops.
|
||||
asm volatile("sync" : : : "memory");
|
||||
}
|
||||
#define LEVELDB_HAVE_MEMORY_BARRIER
|
||||
|
||||
@@ -128,6 +147,66 @@ class AtomicPointer {
|
||||
}
|
||||
};
|
||||
|
||||
// Atomic pointer based on sparc memory barriers
|
||||
#elif defined(__sparcv9) && defined(__GNUC__)
|
||||
class AtomicPointer {
|
||||
private:
|
||||
void* rep_;
|
||||
public:
|
||||
AtomicPointer() { }
|
||||
explicit AtomicPointer(void* v) : rep_(v) { }
|
||||
inline void* Acquire_Load() const {
|
||||
void* val;
|
||||
__asm__ __volatile__ (
|
||||
"ldx [%[rep_]], %[val] \n\t"
|
||||
"membar #LoadLoad|#LoadStore \n\t"
|
||||
: [val] "=r" (val)
|
||||
: [rep_] "r" (&rep_)
|
||||
: "memory");
|
||||
return val;
|
||||
}
|
||||
inline void Release_Store(void* v) {
|
||||
__asm__ __volatile__ (
|
||||
"membar #LoadStore|#StoreStore \n\t"
|
||||
"stx %[v], [%[rep_]] \n\t"
|
||||
:
|
||||
: [rep_] "r" (&rep_), [v] "r" (v)
|
||||
: "memory");
|
||||
}
|
||||
inline void* NoBarrier_Load() const { return rep_; }
|
||||
inline void NoBarrier_Store(void* v) { rep_ = v; }
|
||||
};
|
||||
|
||||
// Atomic pointer based on ia64 acq/rel
|
||||
#elif defined(__ia64) && defined(__GNUC__)
|
||||
class AtomicPointer {
|
||||
private:
|
||||
void* rep_;
|
||||
public:
|
||||
AtomicPointer() { }
|
||||
explicit AtomicPointer(void* v) : rep_(v) { }
|
||||
inline void* Acquire_Load() const {
|
||||
void* val ;
|
||||
__asm__ __volatile__ (
|
||||
"ld8.acq %[val] = [%[rep_]] \n\t"
|
||||
: [val] "=r" (val)
|
||||
: [rep_] "r" (&rep_)
|
||||
: "memory"
|
||||
);
|
||||
return val;
|
||||
}
|
||||
inline void Release_Store(void* v) {
|
||||
__asm__ __volatile__ (
|
||||
"st8.rel [%[rep_]] = %[v] \n\t"
|
||||
:
|
||||
: [rep_] "r" (&rep_), [v] "r" (v)
|
||||
: "memory"
|
||||
);
|
||||
}
|
||||
inline void* NoBarrier_Load() const { return rep_; }
|
||||
inline void NoBarrier_Store(void* v) { rep_ = v; }
|
||||
};
|
||||
|
||||
// We have neither MemoryBarrier(), nor <cstdatomic>
|
||||
#else
|
||||
#error Please implement AtomicPointer for this platform.
|
||||
@@ -137,6 +216,7 @@ class AtomicPointer {
|
||||
#undef LEVELDB_HAVE_MEMORY_BARRIER
|
||||
#undef ARCH_CPU_X86_FAMILY
|
||||
#undef ARCH_CPU_ARM_FAMILY
|
||||
#undef ARCH_CPU_PPC_FAMILY
|
||||
|
||||
} // namespace port
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
# include "port/port_posix.h"
|
||||
#elif defined(LEVELDB_PLATFORM_CHROMIUM)
|
||||
# include "port/port_chromium.h"
|
||||
#elif defined(LEVELDB_PLATFORM_ANDROID)
|
||||
# include "port/port_android.h"
|
||||
#endif
|
||||
|
||||
#endif // STORAGE_LEVELDB_PORT_PORT_H_
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// 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 "port/port_android.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" {
|
||||
size_t fread_unlocked(void *a, size_t b, size_t c, FILE *d) {
|
||||
return fread(a, b, c, d);
|
||||
}
|
||||
|
||||
size_t fwrite_unlocked(const void *a, size_t b, size_t c, FILE *d) {
|
||||
return fwrite(a, b, c, d);
|
||||
}
|
||||
|
||||
int fflush_unlocked(FILE *f) {
|
||||
return fflush(f);
|
||||
}
|
||||
|
||||
int fdatasync(int fd) {
|
||||
return fsync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
namespace leveldb {
|
||||
namespace port {
|
||||
|
||||
static void PthreadCall(const char* label, int result) {
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "pthread %s: %s\n", label, strerror(result));
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
Mutex::Mutex() { PthreadCall("init mutex", pthread_mutex_init(&mu_, NULL)); }
|
||||
Mutex::~Mutex() { PthreadCall("destroy mutex", pthread_mutex_destroy(&mu_)); }
|
||||
void Mutex::Lock() { PthreadCall("lock", pthread_mutex_lock(&mu_)); }
|
||||
void Mutex::Unlock() { PthreadCall("unlock", pthread_mutex_unlock(&mu_)); }
|
||||
|
||||
CondVar::CondVar(Mutex* mu)
|
||||
: mu_(mu) {
|
||||
PthreadCall("init cv", pthread_cond_init(&cv_, NULL));
|
||||
}
|
||||
|
||||
CondVar::~CondVar() {
|
||||
PthreadCall("destroy cv", pthread_cond_destroy(&cv_));
|
||||
}
|
||||
|
||||
void CondVar::Wait() {
|
||||
PthreadCall("wait", pthread_cond_wait(&cv_, &mu_->mu_));
|
||||
}
|
||||
|
||||
void CondVar::Signal(){
|
||||
PthreadCall("signal", pthread_cond_signal(&cv_));
|
||||
}
|
||||
|
||||
void CondVar::SignalAll() {
|
||||
PthreadCall("broadcast", pthread_cond_broadcast(&cv_));
|
||||
}
|
||||
|
||||
} // namespace port
|
||||
} // namespace leveldb
|
||||
@@ -1,159 +0,0 @@
|
||||
// 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.
|
||||
//
|
||||
// See port_example.h for documentation for the following types/functions.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_PORT_PORT_ANDROID_H_
|
||||
#define STORAGE_LEVELDB_PORT_PORT_ANDROID_H_
|
||||
|
||||
#include <endian.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <cctype>
|
||||
|
||||
// Collapse the plethora of ARM flavors available to an easier to manage set
|
||||
// Defs reference is at https://wiki.edubuntu.org/ARM/Thumb2PortingHowto
|
||||
#if defined(__ARM_ARCH_6__) || \
|
||||
defined(__ARM_ARCH_6J__) || \
|
||||
defined(__ARM_ARCH_6K__) || \
|
||||
defined(__ARM_ARCH_6Z__) || \
|
||||
defined(__ARM_ARCH_6T2__) || \
|
||||
defined(__ARM_ARCH_6ZK__) || \
|
||||
defined(__ARM_ARCH_7__) || \
|
||||
defined(__ARM_ARCH_7R__) || \
|
||||
defined(__ARM_ARCH_7A__)
|
||||
#define ARMV6_OR_7 1
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
size_t fread_unlocked(void *a, size_t b, size_t c, FILE *d);
|
||||
size_t fwrite_unlocked(const void *a, size_t b, size_t c, FILE *d);
|
||||
int fflush_unlocked(FILE *f);
|
||||
int fdatasync (int fd);
|
||||
}
|
||||
|
||||
namespace leveldb {
|
||||
namespace port {
|
||||
|
||||
static const bool kLittleEndian = __BYTE_ORDER == __LITTLE_ENDIAN;
|
||||
|
||||
class CondVar;
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
~Mutex();
|
||||
|
||||
void Lock();
|
||||
void Unlock();
|
||||
void AssertHeld() {
|
||||
//TODO(gabor): How can I implement this?
|
||||
}
|
||||
|
||||
private:
|
||||
friend class CondVar;
|
||||
pthread_mutex_t mu_;
|
||||
|
||||
// No copying
|
||||
Mutex(const Mutex&);
|
||||
void operator=(const Mutex&);
|
||||
};
|
||||
|
||||
class CondVar {
|
||||
public:
|
||||
explicit CondVar(Mutex* mu);
|
||||
~CondVar();
|
||||
void Wait();
|
||||
void Signal();
|
||||
void SignalAll();
|
||||
private:
|
||||
Mutex* mu_;
|
||||
pthread_cond_t cv_;
|
||||
};
|
||||
|
||||
#ifndef ARMV6_OR_7
|
||||
// On ARM chipsets <V6, 0xffff0fa0 is the hard coded address of a
|
||||
// memory barrier function provided by the kernel.
|
||||
typedef void (*LinuxKernelMemoryBarrierFunc)(void);
|
||||
// TODO(user): ATTRIBUTE_WEAK is undefined, so this fails to build on
|
||||
// non-ARMV6_OR_7. We may be able to replace it with __attribute__((weak)) for
|
||||
// older ARM builds, but x86 builds will require a different memory barrier.
|
||||
LinuxKernelMemoryBarrierFunc pLinuxKernelMemoryBarrier ATTRIBUTE_WEAK =
|
||||
(LinuxKernelMemoryBarrierFunc) 0xffff0fa0;
|
||||
#endif
|
||||
|
||||
// Storage for a lock-free pointer
|
||||
class AtomicPointer {
|
||||
private:
|
||||
void* rep_;
|
||||
|
||||
inline void MemoryBarrier() const {
|
||||
// TODO(gabor): This only works on Android instruction sets >= V6
|
||||
#ifdef ARMV6_OR_7
|
||||
__asm__ __volatile__("dmb" : : : "memory");
|
||||
#else
|
||||
pLinuxKernelMemoryBarrier();
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
AtomicPointer() { }
|
||||
explicit AtomicPointer(void* v) : rep_(v) { }
|
||||
inline void* Acquire_Load() const {
|
||||
void* r = rep_;
|
||||
MemoryBarrier();
|
||||
return r;
|
||||
}
|
||||
inline void Release_Store(void* v) {
|
||||
MemoryBarrier();
|
||||
rep_ = v;
|
||||
}
|
||||
inline void* NoBarrier_Load() const {
|
||||
void* r = rep_;
|
||||
return r;
|
||||
}
|
||||
inline void NoBarrier_Store(void* v) {
|
||||
rep_ = v;
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(gabor): Implement compress
|
||||
inline bool Snappy_Compress(
|
||||
const char* input,
|
||||
size_t input_length,
|
||||
std::string* output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(gabor): Implement uncompress
|
||||
inline bool Snappy_GetUncompressedLength(const char* input, size_t length,
|
||||
size_t* result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(gabor): Implement uncompress
|
||||
inline bool Snappy_Uncompress(
|
||||
const char* input_data,
|
||||
size_t input_length,
|
||||
char* output) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline uint64_t ThreadIdentifier() {
|
||||
pthread_t tid = pthread_self();
|
||||
uint64_t r = 0;
|
||||
memcpy(&r, &tid, sizeof(r) < sizeof(tid) ? sizeof(r) : sizeof(tid));
|
||||
return r;
|
||||
}
|
||||
|
||||
inline bool GetHeapProfile(void (*func)(void*, const char*, int), void* arg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace port
|
||||
} // namespace leveldb
|
||||
|
||||
#endif // STORAGE_LEVELDB_PORT_PORT_ANDROID_H_
|
||||
@@ -60,6 +60,16 @@ class CondVar {
|
||||
void SignallAll();
|
||||
};
|
||||
|
||||
// Thread-safe initialization.
|
||||
// Used as follows:
|
||||
// static port::OnceType init_control = LEVELDB_ONCE_INIT;
|
||||
// static void Initializer() { ... do something ...; }
|
||||
// ...
|
||||
// port::InitOnce(&init_control, &Initializer);
|
||||
typedef intptr_t OnceType;
|
||||
#define LEVELDB_ONCE_INIT 0
|
||||
extern void InitOnce(port::OnceType*, void (*initializer)());
|
||||
|
||||
// A type that holds a pointer that can be read or written atomically
|
||||
// (i.e., without word-tearing.)
|
||||
class AtomicPointer {
|
||||
|
||||
@@ -46,5 +46,9 @@ void CondVar::SignalAll() {
|
||||
PthreadCall("broadcast", pthread_cond_broadcast(&cv_));
|
||||
}
|
||||
|
||||
void InitOnce(OnceType* once, void (*initializer)()) {
|
||||
PthreadCall("once", pthread_once(once, initializer));
|
||||
}
|
||||
|
||||
} // namespace port
|
||||
} // namespace leveldb
|
||||
|
||||
@@ -7,22 +7,40 @@
|
||||
#ifndef STORAGE_LEVELDB_PORT_PORT_POSIX_H_
|
||||
#define STORAGE_LEVELDB_PORT_PORT_POSIX_H_
|
||||
|
||||
#undef PLATFORM_IS_LITTLE_ENDIAN
|
||||
#if defined(OS_MACOSX)
|
||||
#include <machine/endian.h>
|
||||
#if defined(__DARWIN_LITTLE_ENDIAN) && defined(__DARWIN_BYTE_ORDER)
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN \
|
||||
(__DARWIN_BYTE_ORDER == __DARWIN_LITTLE_ENDIAN)
|
||||
#endif
|
||||
#elif defined(OS_SOLARIS)
|
||||
#include <sys/isa_defs.h>
|
||||
#ifdef _LITTLE_ENDIAN
|
||||
#define LITTLE_ENDIAN
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN true
|
||||
#else
|
||||
#define BIG_ENDIAN
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN false
|
||||
#endif
|
||||
#elif defined(OS_FREEBSD) || defined(OS_OPENBSD) || defined(OS_NETBSD) ||\
|
||||
#elif defined(OS_FREEBSD)
|
||||
#include <sys/types.h>
|
||||
#include <sys/endian.h>
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN (_BYTE_ORDER == _LITTLE_ENDIAN)
|
||||
#elif defined(OS_OPENBSD) || defined(OS_NETBSD) ||\
|
||||
defined(OS_DRAGONFLYBSD)
|
||||
#include <sys/types.h>
|
||||
#include <sys/endian.h>
|
||||
#elif defined(OS_HPUX)
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN false
|
||||
#elif defined(OS_ANDROID)
|
||||
// Due to a bug in the NDK x86 <sys/endian.h> definition,
|
||||
// _BYTE_ORDER must be used instead of __BYTE_ORDER on Android.
|
||||
// See http://code.google.com/p/android/issues/detail?id=39824
|
||||
#include <endian.h>
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN (_BYTE_ORDER == _LITTLE_ENDIAN)
|
||||
#else
|
||||
#include <endian.h>
|
||||
#endif
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef SNAPPY
|
||||
#include <snappy.h>
|
||||
@@ -31,14 +49,13 @@
|
||||
#include <string>
|
||||
#include "port/atomic_pointer.h"
|
||||
|
||||
#ifdef LITTLE_ENDIAN
|
||||
#define IS_LITTLE_ENDIAN true
|
||||
#else
|
||||
#define IS_LITTLE_ENDIAN (__BYTE_ORDER == __LITTLE_ENDIAN)
|
||||
#ifndef PLATFORM_IS_LITTLE_ENDIAN
|
||||
#define PLATFORM_IS_LITTLE_ENDIAN (__BYTE_ORDER == __LITTLE_ENDIAN)
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX) || defined(OS_SOLARIS) || defined(OS_FREEBSD) ||\
|
||||
defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_DRAGONFLYBSD)
|
||||
defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_DRAGONFLYBSD) ||\
|
||||
defined(OS_ANDROID) || defined(OS_HPUX)
|
||||
// Use fread/fwrite/fflush on platforms without _unlocked variants
|
||||
#define fread_unlocked fread
|
||||
#define fwrite_unlocked fwrite
|
||||
@@ -51,10 +68,17 @@
|
||||
#define fdatasync fsync
|
||||
#endif
|
||||
|
||||
#if defined(OS_ANDROID) && __ANDROID_API__ < 9
|
||||
// fdatasync() was only introduced in API level 9 on Android. Use fsync()
|
||||
// when targetting older platforms.
|
||||
#define fdatasync fsync
|
||||
#endif
|
||||
|
||||
namespace leveldb {
|
||||
namespace port {
|
||||
|
||||
static const bool kLittleEndian = IS_LITTLE_ENDIAN;
|
||||
static const bool kLittleEndian = PLATFORM_IS_LITTLE_ENDIAN;
|
||||
#undef PLATFORM_IS_LITTLE_ENDIAN
|
||||
|
||||
class CondVar;
|
||||
|
||||
@@ -88,6 +112,10 @@ class CondVar {
|
||||
Mutex* mu_;
|
||||
};
|
||||
|
||||
typedef pthread_once_t OnceType;
|
||||
#define LEVELDB_ONCE_INIT PTHREAD_ONCE_INIT
|
||||
extern void InitOnce(OnceType* once, void (*initializer)());
|
||||
|
||||
inline bool Snappy_Compress(const char* input, size_t length,
|
||||
::std::string* output) {
|
||||
#ifdef SNAPPY
|
||||
|
||||
59
port/thread_annotations.h
Normal file
59
port/thread_annotations.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
#ifndef STORAGE_LEVELDB_PORT_THREAD_ANNOTATIONS_H
|
||||
|
||||
// Some environments provide custom macros to aid in static thread-safety
|
||||
// analysis. Provide empty definitions of such macros unless they are already
|
||||
// defined.
|
||||
|
||||
#ifndef EXCLUSIVE_LOCKS_REQUIRED
|
||||
#define EXCLUSIVE_LOCKS_REQUIRED(...)
|
||||
#endif
|
||||
|
||||
#ifndef SHARED_LOCKS_REQUIRED
|
||||
#define SHARED_LOCKS_REQUIRED(...)
|
||||
#endif
|
||||
|
||||
#ifndef LOCKS_EXCLUDED
|
||||
#define LOCKS_EXCLUDED(...)
|
||||
#endif
|
||||
|
||||
#ifndef LOCK_RETURNED
|
||||
#define LOCK_RETURNED(x)
|
||||
#endif
|
||||
|
||||
#ifndef LOCKABLE
|
||||
#define LOCKABLE
|
||||
#endif
|
||||
|
||||
#ifndef SCOPED_LOCKABLE
|
||||
#define SCOPED_LOCKABLE
|
||||
#endif
|
||||
|
||||
#ifndef EXCLUSIVE_LOCK_FUNCTION
|
||||
#define EXCLUSIVE_LOCK_FUNCTION(...)
|
||||
#endif
|
||||
|
||||
#ifndef SHARED_LOCK_FUNCTION
|
||||
#define SHARED_LOCK_FUNCTION(...)
|
||||
#endif
|
||||
|
||||
#ifndef EXCLUSIVE_TRYLOCK_FUNCTION
|
||||
#define EXCLUSIVE_TRYLOCK_FUNCTION(...)
|
||||
#endif
|
||||
|
||||
#ifndef SHARED_TRYLOCK_FUNCTION
|
||||
#define SHARED_TRYLOCK_FUNCTION(...)
|
||||
#endif
|
||||
|
||||
#ifndef UNLOCK_FUNCTION
|
||||
#define UNLOCK_FUNCTION(...)
|
||||
#endif
|
||||
|
||||
#ifndef NO_THREAD_SAFETY_ANALYSIS
|
||||
#define NO_THREAD_SAFETY_ANALYSIS
|
||||
#endif
|
||||
|
||||
#endif // STORAGE_LEVELDB_PORT_THREAD_ANNOTATIONS_H
|
||||
@@ -16,7 +16,7 @@
|
||||
namespace leveldb {
|
||||
|
||||
inline uint32_t Block::NumRestarts() const {
|
||||
assert(size_ >= 2*sizeof(uint32_t));
|
||||
assert(size_ >= sizeof(uint32_t));
|
||||
return DecodeFixed32(data_ + size_ - sizeof(uint32_t));
|
||||
}
|
||||
|
||||
@@ -27,11 +27,12 @@ Block::Block(const BlockContents& contents)
|
||||
if (size_ < sizeof(uint32_t)) {
|
||||
size_ = 0; // Error marker
|
||||
} else {
|
||||
restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t);
|
||||
if (restart_offset_ > size_ - sizeof(uint32_t)) {
|
||||
// The size is too small for NumRestarts() and therefore
|
||||
// restart_offset_ wrapped around.
|
||||
size_t max_restarts_allowed = (size_-sizeof(uint32_t)) / sizeof(uint32_t);
|
||||
if (NumRestarts() > max_restarts_allowed) {
|
||||
// The size is too small for NumRestarts()
|
||||
size_ = 0;
|
||||
} else {
|
||||
restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,8 +163,8 @@ class Block::Iter : public Iterator {
|
||||
}
|
||||
|
||||
virtual void Seek(const Slice& target) {
|
||||
// Binary search in restart array to find the first restart point
|
||||
// with a key >= target
|
||||
// Binary search in restart array to find the last restart point
|
||||
// with a key < target
|
||||
uint32_t left = 0;
|
||||
uint32_t right = num_restarts_ - 1;
|
||||
while (left < right) {
|
||||
@@ -253,7 +254,7 @@ class Block::Iter : public Iterator {
|
||||
};
|
||||
|
||||
Iterator* Block::NewIterator(const Comparator* cmp) {
|
||||
if (size_ < 2*sizeof(uint32_t)) {
|
||||
if (size_ < sizeof(uint32_t)) {
|
||||
return NewErrorIterator(Status::Corruption("bad block contents"));
|
||||
}
|
||||
const uint32_t num_restarts = NumRestarts();
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestHashFilter : public FilterPolicy {
|
||||
|
||||
virtual bool KeyMayMatch(const Slice& key, const Slice& filter) const {
|
||||
uint32_t h = Hash(key.data(), key.size(), 1);
|
||||
for (int i = 0; i + 4 <= filter.size(); i += 4) {
|
||||
for (size_t i = 0; i + 4 <= filter.size(); i += 4) {
|
||||
if (h == DecodeFixed32(filter.data() + i)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -228,7 +228,6 @@ Status Table::InternalGet(const ReadOptions& options, const Slice& k,
|
||||
!filter->KeyMayMatch(handle.offset(), k)) {
|
||||
// Not found
|
||||
} else {
|
||||
Slice handle = iiter->value();
|
||||
Iterator* block_iter = BlockReader(this, options, iiter->value());
|
||||
block_iter->Seek(k);
|
||||
if (block_iter->Valid()) {
|
||||
|
||||
@@ -644,6 +644,36 @@ class Harness {
|
||||
Constructor* constructor_;
|
||||
};
|
||||
|
||||
// Test empty table/block.
|
||||
TEST(Harness, Empty) {
|
||||
for (int i = 0; i < kNumTestArgs; i++) {
|
||||
Init(kTestArgList[i]);
|
||||
Random rnd(test::RandomSeed() + 1);
|
||||
Test(&rnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Special test for a block with no restart entries. The C++ leveldb
|
||||
// code never generates such blocks, but the Java version of leveldb
|
||||
// seems to.
|
||||
TEST(Harness, ZeroRestartPointsInBlock) {
|
||||
char data[sizeof(uint32_t)];
|
||||
memset(data, 0, sizeof(data));
|
||||
BlockContents contents;
|
||||
contents.data = Slice(data, sizeof(data));
|
||||
contents.cachable = false;
|
||||
contents.heap_allocated = false;
|
||||
Block block(contents);
|
||||
Iterator* iter = block.NewIterator(BytewiseComparator());
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
delete iter;
|
||||
}
|
||||
|
||||
// Test the empty key
|
||||
TEST(Harness, SimpleEmptyKey) {
|
||||
for (int i = 0; i < kNumTestArgs; i++) {
|
||||
|
||||
@@ -40,7 +40,7 @@ char* Arena::AllocateFallback(size_t bytes) {
|
||||
}
|
||||
|
||||
char* Arena::AllocateAligned(size_t bytes) {
|
||||
const int align = sizeof(void*); // We'll align to pointer size
|
||||
const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
|
||||
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);
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
#ifndef STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
#define STORAGE_LEVELDB_UTIL_ARENA_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -40,7 +40,7 @@ TEST(ArenaTest, Simple) {
|
||||
r = arena.Allocate(s);
|
||||
}
|
||||
|
||||
for (int b = 0; b < s; b++) {
|
||||
for (size_t b = 0; b < s; b++) {
|
||||
// Fill the "i"th allocation with a known bit pattern
|
||||
r[b] = i % 256;
|
||||
}
|
||||
@@ -51,10 +51,10 @@ TEST(ArenaTest, Simple) {
|
||||
ASSERT_LE(arena.MemoryUsage(), bytes * 1.10);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < allocated.size(); i++) {
|
||||
for (size_t i = 0; i < allocated.size(); i++) {
|
||||
size_t num_bytes = allocated[i].first;
|
||||
const char* p = allocated[i].second;
|
||||
for (int b = 0; b < num_bytes; b++) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "leveldb/filter_policy.h"
|
||||
|
||||
#include "util/coding.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
@@ -13,8 +14,8 @@ namespace leveldb {
|
||||
static const int kVerbose = 1;
|
||||
|
||||
static Slice Key(int i, char* buffer) {
|
||||
memcpy(buffer, &i, sizeof(i));
|
||||
return Slice(buffer, sizeof(i));
|
||||
EncodeFixed32(buffer, i);
|
||||
return Slice(buffer, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
class BloomTest {
|
||||
@@ -125,7 +126,8 @@ TEST(BloomTest, VaryingLengths) {
|
||||
}
|
||||
Build();
|
||||
|
||||
ASSERT_LE(FilterSize(), (length * 10 / 8) + 40) << length;
|
||||
ASSERT_LE(FilterSize(), static_cast<size_t>((length * 10 / 8) + 40))
|
||||
<< length;
|
||||
|
||||
// All added keys must match
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
||||
@@ -116,7 +116,6 @@ class HandleTable {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != NULL) {
|
||||
LRUHandle* next = h->next_hash;
|
||||
Slice key = h->key();
|
||||
uint32_t hash = h->hash;
|
||||
LRUHandle** ptr = &new_list[hash & (new_length - 1)];
|
||||
h->next_hash = *ptr;
|
||||
@@ -160,7 +159,6 @@ class LRUCache {
|
||||
// mutex_ protects the following state.
|
||||
port::Mutex mutex_;
|
||||
size_t usage_;
|
||||
uint64_t last_id_;
|
||||
|
||||
// Dummy head of LRU list.
|
||||
// lru.prev is newest entry, lru.next is oldest entry.
|
||||
@@ -170,8 +168,7 @@ class LRUCache {
|
||||
};
|
||||
|
||||
LRUCache::LRUCache()
|
||||
: usage_(0),
|
||||
last_id_(0) {
|
||||
: usage_(0) {
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
|
||||
@@ -7,29 +7,29 @@
|
||||
namespace leveldb {
|
||||
|
||||
void EncodeFixed32(char* buf, uint32_t value) {
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
memcpy(buf, &value, sizeof(value));
|
||||
#else
|
||||
buf[0] = value & 0xff;
|
||||
buf[1] = (value >> 8) & 0xff;
|
||||
buf[2] = (value >> 16) & 0xff;
|
||||
buf[3] = (value >> 24) & 0xff;
|
||||
#endif
|
||||
if (port::kLittleEndian) {
|
||||
memcpy(buf, &value, sizeof(value));
|
||||
} else {
|
||||
buf[0] = value & 0xff;
|
||||
buf[1] = (value >> 8) & 0xff;
|
||||
buf[2] = (value >> 16) & 0xff;
|
||||
buf[3] = (value >> 24) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeFixed64(char* buf, uint64_t value) {
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
memcpy(buf, &value, sizeof(value));
|
||||
#else
|
||||
buf[0] = value & 0xff;
|
||||
buf[1] = (value >> 8) & 0xff;
|
||||
buf[2] = (value >> 16) & 0xff;
|
||||
buf[3] = (value >> 24) & 0xff;
|
||||
buf[4] = (value >> 32) & 0xff;
|
||||
buf[5] = (value >> 40) & 0xff;
|
||||
buf[6] = (value >> 48) & 0xff;
|
||||
buf[7] = (value >> 56) & 0xff;
|
||||
#endif
|
||||
if (port::kLittleEndian) {
|
||||
memcpy(buf, &value, sizeof(value));
|
||||
} else {
|
||||
buf[0] = value & 0xff;
|
||||
buf[1] = (value >> 8) & 0xff;
|
||||
buf[2] = (value >> 16) & 0xff;
|
||||
buf[3] = (value >> 24) & 0xff;
|
||||
buf[4] = (value >> 32) & 0xff;
|
||||
buf[5] = (value >> 40) & 0xff;
|
||||
buf[6] = (value >> 48) & 0xff;
|
||||
buf[7] = (value >> 56) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
void PutFixed32(std::string* dst, uint32_t value) {
|
||||
|
||||
@@ -51,6 +51,29 @@ TEST(Coding, Fixed64) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
@@ -86,16 +109,16 @@ TEST(Coding, Varint64) {
|
||||
values.push_back(power);
|
||||
values.push_back(power-1);
|
||||
values.push_back(power+1);
|
||||
};
|
||||
}
|
||||
|
||||
std::string s;
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
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 (int i = 0; i < values.size(); i++) {
|
||||
for (size_t i = 0; i < values.size(); i++) {
|
||||
ASSERT_TRUE(p < limit);
|
||||
uint64_t actual;
|
||||
const char* start = p;
|
||||
@@ -120,7 +143,7 @@ TEST(Coding, Varint32Truncation) {
|
||||
std::string s;
|
||||
PutVarint32(&s, large_value);
|
||||
uint32_t result;
|
||||
for (int len = 0; len < s.size() - 1; len++) {
|
||||
for (size_t len = 0; len < s.size() - 1; len++) {
|
||||
ASSERT_TRUE(GetVarint32Ptr(s.data(), s.data() + len, &result) == NULL);
|
||||
}
|
||||
ASSERT_TRUE(GetVarint32Ptr(s.data(), s.data() + s.size(), &result) != NULL);
|
||||
@@ -139,7 +162,7 @@ TEST(Coding, Varint64Truncation) {
|
||||
std::string s;
|
||||
PutVarint64(&s, large_value);
|
||||
uint64_t result;
|
||||
for (int len = 0; len < s.size() - 1; len++) {
|
||||
for (size_t len = 0; len < s.size() - 1; len++) {
|
||||
ASSERT_TRUE(GetVarint64Ptr(s.data(), s.data() + len, &result) == NULL);
|
||||
}
|
||||
ASSERT_TRUE(GetVarint64Ptr(s.data(), s.data() + s.size(), &result) != NULL);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <stdint.h>
|
||||
#include "leveldb/comparator.h"
|
||||
#include "leveldb/slice.h"
|
||||
#include "port/port.h"
|
||||
#include "util/logging.h"
|
||||
|
||||
namespace leveldb {
|
||||
@@ -65,11 +66,15 @@ class BytewiseComparatorImpl : public Comparator {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Intentionally not destroyed to prevent destructor racing
|
||||
// with background threads.
|
||||
static const Comparator* bytewise = new BytewiseComparatorImpl;
|
||||
static port::OnceType once = LEVELDB_ONCE_INIT;
|
||||
static const Comparator* bytewise;
|
||||
|
||||
static void InitModule() {
|
||||
bytewise = new BytewiseComparatorImpl;
|
||||
}
|
||||
|
||||
const Comparator* BytewiseComparator() {
|
||||
port::InitOnce(&once, InitModule);
|
||||
return bytewise;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <deque>
|
||||
#include <set>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
@@ -23,6 +24,7 @@
|
||||
#include "leveldb/slice.h"
|
||||
#include "port/port.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/posix_logger.h"
|
||||
|
||||
namespace leveldb {
|
||||
@@ -90,18 +92,75 @@ class PosixRandomAccessFile: public RandomAccessFile {
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class to limit mmap file usage so that we do not end up
|
||||
// running out virtual memory or running into kernel performance
|
||||
// problems for very large databases.
|
||||
class MmapLimiter {
|
||||
public:
|
||||
// Up to 1000 mmaps for 64-bit binaries; none for smaller pointer sizes.
|
||||
MmapLimiter() {
|
||||
SetAllowed(sizeof(void*) >= 8 ? 1000 : 0);
|
||||
}
|
||||
|
||||
// If another mmap slot is available, acquire it and return true.
|
||||
// Else return false.
|
||||
bool Acquire() {
|
||||
if (GetAllowed() <= 0) {
|
||||
return false;
|
||||
}
|
||||
MutexLock l(&mu_);
|
||||
intptr_t x = GetAllowed();
|
||||
if (x <= 0) {
|
||||
return false;
|
||||
} else {
|
||||
SetAllowed(x - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Release a slot acquired by a previous call to Acquire() that returned true.
|
||||
void Release() {
|
||||
MutexLock l(&mu_);
|
||||
SetAllowed(GetAllowed() + 1);
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::AtomicPointer allowed_;
|
||||
|
||||
intptr_t GetAllowed() const {
|
||||
return reinterpret_cast<intptr_t>(allowed_.Acquire_Load());
|
||||
}
|
||||
|
||||
// REQUIRES: mu_ must be held
|
||||
void SetAllowed(intptr_t v) {
|
||||
allowed_.Release_Store(reinterpret_cast<void*>(v));
|
||||
}
|
||||
|
||||
MmapLimiter(const MmapLimiter&);
|
||||
void operator=(const MmapLimiter&);
|
||||
};
|
||||
|
||||
// mmap() based random-access
|
||||
class PosixMmapReadableFile: public RandomAccessFile {
|
||||
private:
|
||||
std::string filename_;
|
||||
void* mmapped_region_;
|
||||
size_t length_;
|
||||
MmapLimiter* limiter_;
|
||||
|
||||
public:
|
||||
// base[0,length-1] contains the mmapped contents of the file.
|
||||
PosixMmapReadableFile(const std::string& fname, void* base, size_t length)
|
||||
: filename_(fname), mmapped_region_(base), length_(length) { }
|
||||
virtual ~PosixMmapReadableFile() { munmap(mmapped_region_, length_); }
|
||||
PosixMmapReadableFile(const std::string& fname, void* base, size_t length,
|
||||
MmapLimiter* limiter)
|
||||
: filename_(fname), mmapped_region_(base), length_(length),
|
||||
limiter_(limiter) {
|
||||
}
|
||||
|
||||
virtual ~PosixMmapReadableFile() {
|
||||
munmap(mmapped_region_, length_);
|
||||
limiter_->Release();
|
||||
}
|
||||
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const {
|
||||
@@ -116,172 +175,83 @@ class PosixMmapReadableFile: public RandomAccessFile {
|
||||
}
|
||||
};
|
||||
|
||||
// We preallocate up to an extra megabyte and use memcpy to append new
|
||||
// data to the file. This is safe since we either properly close the
|
||||
// file before reading from it, or for log files, the reading code
|
||||
// knows enough to skip zero suffixes.
|
||||
class PosixMmapFile : public WritableFile {
|
||||
class PosixWritableFile : public WritableFile {
|
||||
private:
|
||||
std::string filename_;
|
||||
int fd_;
|
||||
size_t page_size_;
|
||||
size_t map_size_; // How much extra memory to map at a time
|
||||
char* base_; // The mapped region
|
||||
char* limit_; // Limit of the mapped region
|
||||
char* dst_; // Where to write next (in range [base_,limit_])
|
||||
char* last_sync_; // Where have we synced up to
|
||||
uint64_t file_offset_; // Offset of base_ in file
|
||||
|
||||
// Have we done an munmap of unsynced data?
|
||||
bool pending_sync_;
|
||||
|
||||
// Roundup x to a multiple of y
|
||||
static size_t Roundup(size_t x, size_t y) {
|
||||
return ((x + y - 1) / y) * y;
|
||||
}
|
||||
|
||||
size_t TruncateToPageBoundary(size_t s) {
|
||||
s -= (s & (page_size_ - 1));
|
||||
assert((s % page_size_) == 0);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool UnmapCurrentRegion() {
|
||||
bool result = true;
|
||||
if (base_ != NULL) {
|
||||
if (last_sync_ < limit_) {
|
||||
// Defer syncing this data until next Sync() call, if any
|
||||
pending_sync_ = true;
|
||||
}
|
||||
if (munmap(base_, limit_ - base_) != 0) {
|
||||
result = false;
|
||||
}
|
||||
file_offset_ += limit_ - base_;
|
||||
base_ = NULL;
|
||||
limit_ = NULL;
|
||||
last_sync_ = NULL;
|
||||
dst_ = NULL;
|
||||
|
||||
// Increase the amount we map the next time, but capped at 1MB
|
||||
if (map_size_ < (1<<20)) {
|
||||
map_size_ *= 2;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MapNewRegion() {
|
||||
assert(base_ == NULL);
|
||||
if (ftruncate(fd_, file_offset_ + map_size_) < 0) {
|
||||
return false;
|
||||
}
|
||||
void* ptr = mmap(NULL, map_size_, PROT_READ | PROT_WRITE, MAP_SHARED,
|
||||
fd_, file_offset_);
|
||||
if (ptr == MAP_FAILED) {
|
||||
return false;
|
||||
}
|
||||
base_ = reinterpret_cast<char*>(ptr);
|
||||
limit_ = base_ + map_size_;
|
||||
dst_ = base_;
|
||||
last_sync_ = base_;
|
||||
return true;
|
||||
}
|
||||
FILE* file_;
|
||||
|
||||
public:
|
||||
PosixMmapFile(const std::string& fname, int fd, size_t page_size)
|
||||
: filename_(fname),
|
||||
fd_(fd),
|
||||
page_size_(page_size),
|
||||
map_size_(Roundup(65536, page_size)),
|
||||
base_(NULL),
|
||||
limit_(NULL),
|
||||
dst_(NULL),
|
||||
last_sync_(NULL),
|
||||
file_offset_(0),
|
||||
pending_sync_(false) {
|
||||
assert((page_size & (page_size - 1)) == 0);
|
||||
}
|
||||
PosixWritableFile(const std::string& fname, FILE* f)
|
||||
: filename_(fname), file_(f) { }
|
||||
|
||||
|
||||
~PosixMmapFile() {
|
||||
if (fd_ >= 0) {
|
||||
PosixMmapFile::Close();
|
||||
~PosixWritableFile() {
|
||||
if (file_ != NULL) {
|
||||
// Ignoring any potential errors
|
||||
fclose(file_);
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status Append(const Slice& data) {
|
||||
const char* src = data.data();
|
||||
size_t left = data.size();
|
||||
while (left > 0) {
|
||||
assert(base_ <= dst_);
|
||||
assert(dst_ <= limit_);
|
||||
size_t avail = limit_ - dst_;
|
||||
if (avail == 0) {
|
||||
if (!UnmapCurrentRegion() ||
|
||||
!MapNewRegion()) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
}
|
||||
|
||||
size_t n = (left <= avail) ? left : avail;
|
||||
memcpy(dst_, src, n);
|
||||
dst_ += n;
|
||||
src += n;
|
||||
left -= n;
|
||||
size_t r = fwrite_unlocked(data.data(), 1, data.size(), file_);
|
||||
if (r != data.size()) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status Close() {
|
||||
Status s;
|
||||
size_t unused = limit_ - dst_;
|
||||
if (!UnmapCurrentRegion()) {
|
||||
s = IOError(filename_, errno);
|
||||
} else if (unused > 0) {
|
||||
// Trim the extra space at the end of the file
|
||||
if (ftruncate(fd_, file_offset_ - unused) < 0) {
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
Status result;
|
||||
if (fclose(file_) != 0) {
|
||||
result = IOError(filename_, errno);
|
||||
}
|
||||
|
||||
if (close(fd_) < 0) {
|
||||
if (s.ok()) {
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
}
|
||||
|
||||
fd_ = -1;
|
||||
base_ = NULL;
|
||||
limit_ = NULL;
|
||||
return s;
|
||||
file_ = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual Status Flush() {
|
||||
if (fflush_unlocked(file_) != 0) {
|
||||
return IOError(filename_, errno);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status Sync() {
|
||||
Status SyncDirIfManifest() {
|
||||
const char* f = filename_.c_str();
|
||||
const char* sep = strrchr(f, '/');
|
||||
Slice basename;
|
||||
std::string dir;
|
||||
if (sep == NULL) {
|
||||
dir = ".";
|
||||
basename = f;
|
||||
} else {
|
||||
dir = std::string(f, sep - f);
|
||||
basename = sep + 1;
|
||||
}
|
||||
Status s;
|
||||
|
||||
if (pending_sync_) {
|
||||
// Some unmapped data was not synced
|
||||
pending_sync_ = false;
|
||||
if (fdatasync(fd_) < 0) {
|
||||
s = IOError(filename_, errno);
|
||||
if (basename.starts_with("MANIFEST")) {
|
||||
int fd = open(dir.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
s = IOError(dir, errno);
|
||||
} else {
|
||||
if (fsync(fd) < 0) {
|
||||
s = IOError(dir, errno);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
if (dst_ > last_sync_) {
|
||||
// Find the beginnings of the pages that contain the first and last
|
||||
// bytes to be synced.
|
||||
size_t p1 = TruncateToPageBoundary(last_sync_ - base_);
|
||||
size_t p2 = TruncateToPageBoundary(dst_ - base_ - 1);
|
||||
last_sync_ = dst_;
|
||||
if (msync(base_ + p1, p2 - p1 + page_size_, MS_SYNC) < 0) {
|
||||
s = IOError(filename_, errno);
|
||||
}
|
||||
virtual Status Sync() {
|
||||
// Ensure new files referred to by the manifest are in the filesystem.
|
||||
Status s = SyncDirIfManifest();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (fflush_unlocked(file_) != 0 ||
|
||||
fdatasync(fileno(file_)) != 0) {
|
||||
s = Status::IOError(filename_, strerror(errno));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
};
|
||||
@@ -300,6 +270,25 @@ static int LockOrUnlock(int fd, bool lock) {
|
||||
class PosixFileLock : public FileLock {
|
||||
public:
|
||||
int fd_;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
// Set of locked files. We keep a separate set instead of just
|
||||
// relying on fcntrl(F_SETLK) since fcntl(F_SETLK) does not provide
|
||||
// any protection against multiple uses from the same process.
|
||||
class PosixLockTable {
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
std::set<std::string> locked_files_;
|
||||
public:
|
||||
bool Insert(const std::string& fname) {
|
||||
MutexLock l(&mu_);
|
||||
return locked_files_.insert(fname).second;
|
||||
}
|
||||
void Remove(const std::string& fname) {
|
||||
MutexLock l(&mu_);
|
||||
locked_files_.erase(fname);
|
||||
}
|
||||
};
|
||||
|
||||
class PosixEnv : public Env {
|
||||
@@ -307,7 +296,7 @@ class PosixEnv : public Env {
|
||||
PosixEnv();
|
||||
virtual ~PosixEnv() {
|
||||
fprintf(stderr, "Destroying Env::Default()\n");
|
||||
exit(1);
|
||||
abort();
|
||||
}
|
||||
|
||||
virtual Status NewSequentialFile(const std::string& fname,
|
||||
@@ -329,19 +318,21 @@ class PosixEnv : public Env {
|
||||
int fd = open(fname.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
s = IOError(fname, errno);
|
||||
} else if (sizeof(void*) >= 8) {
|
||||
// Use mmap when virtual address-space is plentiful.
|
||||
} else if (mmap_limit_.Acquire()) {
|
||||
uint64_t size;
|
||||
s = GetFileSize(fname, &size);
|
||||
if (s.ok()) {
|
||||
void* base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (base != MAP_FAILED) {
|
||||
*result = new PosixMmapReadableFile(fname, base, size);
|
||||
*result = new PosixMmapReadableFile(fname, base, size, &mmap_limit_);
|
||||
} else {
|
||||
s = IOError(fname, errno);
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
if (!s.ok()) {
|
||||
mmap_limit_.Release();
|
||||
}
|
||||
} else {
|
||||
*result = new PosixRandomAccessFile(fname, fd);
|
||||
}
|
||||
@@ -351,12 +342,12 @@ class PosixEnv : public Env {
|
||||
virtual Status NewWritableFile(const std::string& fname,
|
||||
WritableFile** result) {
|
||||
Status s;
|
||||
const int fd = open(fname.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||
if (fd < 0) {
|
||||
FILE* f = fopen(fname.c_str(), "w");
|
||||
if (f == NULL) {
|
||||
*result = NULL;
|
||||
s = IOError(fname, errno);
|
||||
} else {
|
||||
*result = new PosixMmapFile(fname, fd, page_size_);
|
||||
*result = new PosixWritableFile(fname, f);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -386,7 +377,7 @@ class PosixEnv : public Env {
|
||||
result = IOError(fname, errno);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
virtual Status CreateDir(const std::string& name) {
|
||||
Status result;
|
||||
@@ -394,7 +385,7 @@ class PosixEnv : public Env {
|
||||
result = IOError(name, errno);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
virtual Status DeleteDir(const std::string& name) {
|
||||
Status result;
|
||||
@@ -402,7 +393,7 @@ class PosixEnv : public Env {
|
||||
result = IOError(name, errno);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
virtual Status GetFileSize(const std::string& fname, uint64_t* size) {
|
||||
Status s;
|
||||
@@ -430,12 +421,17 @@ class PosixEnv : public Env {
|
||||
int fd = open(fname.c_str(), O_RDWR | O_CREAT, 0644);
|
||||
if (fd < 0) {
|
||||
result = IOError(fname, errno);
|
||||
} else if (!locks_.Insert(fname)) {
|
||||
close(fd);
|
||||
result = Status::IOError("lock " + fname, "already held by process");
|
||||
} else if (LockOrUnlock(fd, true) == -1) {
|
||||
result = IOError("lock " + fname, errno);
|
||||
close(fd);
|
||||
locks_.Remove(fname);
|
||||
} else {
|
||||
PosixFileLock* my_lock = new PosixFileLock;
|
||||
my_lock->fd_ = fd;
|
||||
my_lock->name_ = fname;
|
||||
*lock = my_lock;
|
||||
}
|
||||
return result;
|
||||
@@ -447,6 +443,7 @@ class PosixEnv : public Env {
|
||||
if (LockOrUnlock(my_lock->fd_, false) == -1) {
|
||||
result = IOError("unlock", errno);
|
||||
}
|
||||
locks_.Remove(my_lock->name_);
|
||||
close(my_lock->fd_);
|
||||
delete my_lock;
|
||||
return result;
|
||||
@@ -502,7 +499,7 @@ class PosixEnv : public Env {
|
||||
void PthreadCall(const char* label, int result) {
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "pthread %s: %s\n", label, strerror(result));
|
||||
exit(1);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,7 +510,6 @@ class PosixEnv : public Env {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t page_size_;
|
||||
pthread_mutex_t mu_;
|
||||
pthread_cond_t bgsignal_;
|
||||
pthread_t bgthread_;
|
||||
@@ -523,10 +519,12 @@ class PosixEnv : public Env {
|
||||
struct BGItem { void* arg; void (*function)(void*); };
|
||||
typedef std::deque<BGItem> BGQueue;
|
||||
BGQueue queue_;
|
||||
|
||||
PosixLockTable locks_;
|
||||
MmapLimiter mmap_limit_;
|
||||
};
|
||||
|
||||
PosixEnv::PosixEnv() : page_size_(getpagesize()),
|
||||
started_bgthread_(false) {
|
||||
PosixEnv::PosixEnv() : started_bgthread_(false) {
|
||||
PthreadCall("mutex_init", pthread_mutex_init(&mu_, NULL));
|
||||
PthreadCall("cvar_init", pthread_cond_init(&bgsignal_, NULL));
|
||||
}
|
||||
|
||||
11
util/hash.cc
11
util/hash.cc
@@ -6,6 +6,13 @@
|
||||
#include "util/coding.h"
|
||||
#include "util/hash.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) {
|
||||
@@ -28,10 +35,10 @@ uint32_t Hash(const char* data, size_t n, uint32_t seed) {
|
||||
switch (limit - data) {
|
||||
case 3:
|
||||
h += data[2] << 16;
|
||||
// fall through
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 2:
|
||||
h += data[1] << 8;
|
||||
// fall through
|
||||
FALLTHROUGH_INTENDED;
|
||||
case 1:
|
||||
h += data[0];
|
||||
h *= m;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define STORAGE_LEVELDB_UTIL_MUTEXLOCK_H_
|
||||
|
||||
#include "port/port.h"
|
||||
#include "port/thread_annotations.h"
|
||||
|
||||
namespace leveldb {
|
||||
|
||||
@@ -19,12 +20,13 @@ namespace leveldb {
|
||||
// ... some complex code, possibly with multiple return paths ...
|
||||
// }
|
||||
|
||||
class MutexLock {
|
||||
class SCOPED_LOCKABLE MutexLock {
|
||||
public:
|
||||
explicit MutexLock(port::Mutex *mu) : mu_(mu) {
|
||||
explicit MutexLock(port::Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu)
|
||||
: mu_(mu) {
|
||||
this->mu_->Lock();
|
||||
}
|
||||
~MutexLock() { this->mu_->Unlock(); }
|
||||
~MutexLock() UNLOCK_FUNCTION() { this->mu_->Unlock(); }
|
||||
|
||||
private:
|
||||
port::Mutex *const mu_;
|
||||
|
||||
@@ -16,7 +16,12 @@ class Random {
|
||||
private:
|
||||
uint32_t seed_;
|
||||
public:
|
||||
explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) { }
|
||||
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
|
||||
|
||||
@@ -38,7 +38,7 @@ int RunAllTests() {
|
||||
|
||||
int num = 0;
|
||||
if (tests != NULL) {
|
||||
for (int i = 0; i < tests->size(); i++) {
|
||||
for (size_t i = 0; i < tests->size(); i++) {
|
||||
const Test& t = (*tests)[i];
|
||||
if (matcher != NULL) {
|
||||
std::string name = t.base;
|
||||
|
||||
@@ -32,7 +32,7 @@ std::string RandomKey(Random* rnd, int len) {
|
||||
|
||||
|
||||
extern Slice CompressibleString(Random* rnd, double compressed_fraction,
|
||||
int len, std::string* dst) {
|
||||
size_t len, std::string* dst) {
|
||||
int raw = static_cast<int>(len * compressed_fraction);
|
||||
if (raw < 1) raw = 1;
|
||||
std::string raw_data;
|
||||
|
||||
@@ -24,7 +24,7 @@ extern std::string RandomKey(Random* rnd, int len);
|
||||
// "N*compressed_fraction" bytes and return a Slice that references
|
||||
// the generated data.
|
||||
extern Slice CompressibleString(Random* rnd, double compressed_fraction,
|
||||
int len, std::string* dst);
|
||||
size_t len, std::string* dst);
|
||||
|
||||
// A wrapper that allows injection of errors.
|
||||
class ErrorEnv : public EnvWrapper {
|
||||
|
||||
Reference in New Issue
Block a user