aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb')
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch.go26
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch_test.go120
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench2_test.go58
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench_test.go464
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/bench2_test.go30
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go554
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go500
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go596
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go281
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_iter.go42
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go8
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_state.go68
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_test.go2665
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_transaction.go289
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_util.go50
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_write.go113
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors.go1
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go24
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/external_test.go58
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/filter/bloom_test.go142
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter_test.go30
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go83
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go11
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter_test.go60
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go3
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go818
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key.go17
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key_test.go133
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go11
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/bench_test.go75
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go9
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go11
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go135
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go91
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/options.go41
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go312
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_compaction.go302
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record.go74
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go64
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_util.go113
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go565
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go17
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go25
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_test.go142
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go37
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go17
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go207
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage_test.go66
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go154
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage_test.go539
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table.go85
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go139
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/reader.go12
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go11
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go122
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/writer.go8
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go206
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go468
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go63
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util.go14
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go1
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_test.go369
-rw-r--r--Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/version.go258
63 files changed, 2746 insertions, 9261 deletions
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch.go
index ccf390c9c..89fcf34bb 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch.go
@@ -12,6 +12,7 @@ import (
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/memdb"
+ "github.com/syndtr/goleveldb/leveldb/storage"
)
type ErrBatchCorrupted struct {
@@ -23,7 +24,7 @@ func (e *ErrBatchCorrupted) Error() string {
}
func newErrBatchCorrupted(reason string) error {
- return errors.NewErrCorrupted(nil, &ErrBatchCorrupted{reason})
+ return errors.NewErrCorrupted(storage.FileDesc{}, &ErrBatchCorrupted{reason})
}
const (
@@ -121,13 +122,14 @@ func (b *Batch) Load(data []byte) error {
// Replay replays batch contents.
func (b *Batch) Replay(r BatchReplay) error {
- return b.decodeRec(func(i int, kt kType, key, value []byte) {
+ return b.decodeRec(func(i int, kt kType, key, value []byte) error {
switch kt {
case ktVal:
r.Put(key, value)
case ktDel:
r.Delete(key)
}
+ return nil
})
}
@@ -193,7 +195,7 @@ func (b *Batch) decode(prevSeq uint64, data []byte) error {
return nil
}
-func (b *Batch) decodeRec(f func(i int, kt kType, key, value []byte)) (err error) {
+func (b *Batch) decodeRec(f func(i int, kt kType, key, value []byte) error) error {
off := batchHdrLen
for i := 0; i < b.rLen; i++ {
if off >= len(b.data) {
@@ -224,16 +226,19 @@ func (b *Batch) decodeRec(f func(i int, kt kType, key, value []byte)) (err error
off += int(x)
}
- f(i, kt, key, value)
+ if err := f(i, kt, key, value); err != nil {
+ return err
+ }
}
return nil
}
func (b *Batch) memReplay(to *memdb.DB) error {
- return b.decodeRec(func(i int, kt kType, key, value []byte) {
- ikey := newIkey(key, b.seq+uint64(i), kt)
- to.Put(ikey, value)
+ var ikScratch []byte
+ return b.decodeRec(func(i int, kt kType, key, value []byte) error {
+ ikScratch = makeIkey(ikScratch, key, b.seq+uint64(i), kt)
+ return to.Put(ikScratch, value)
})
}
@@ -245,8 +250,9 @@ func (b *Batch) memDecodeAndReplay(prevSeq uint64, data []byte, to *memdb.DB) er
}
func (b *Batch) revertMemReplay(to *memdb.DB) error {
- return b.decodeRec(func(i int, kt kType, key, value []byte) {
- ikey := newIkey(key, b.seq+uint64(i), kt)
- to.Delete(ikey)
+ var ikScratch []byte
+ return b.decodeRec(func(i int, kt kType, key, value []byte) error {
+ ikScratch := makeIkey(ikScratch, key, b.seq+uint64(i), kt)
+ return to.Delete(ikScratch)
})
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch_test.go
deleted file mode 100644
index 7fc842f4f..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/batch_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- "bytes"
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
- "github.com/syndtr/goleveldb/leveldb/memdb"
-)
-
-type tbRec struct {
- kt kType
- key, value []byte
-}
-
-type testBatch struct {
- rec []*tbRec
-}
-
-func (p *testBatch) Put(key, value []byte) {
- p.rec = append(p.rec, &tbRec{ktVal, key, value})
-}
-
-func (p *testBatch) Delete(key []byte) {
- p.rec = append(p.rec, &tbRec{ktDel, key, nil})
-}
-
-func compareBatch(t *testing.T, b1, b2 *Batch) {
- if b1.seq != b2.seq {
- t.Errorf("invalid seq number want %d, got %d", b1.seq, b2.seq)
- }
- if b1.Len() != b2.Len() {
- t.Fatalf("invalid record length want %d, got %d", b1.Len(), b2.Len())
- }
- p1, p2 := new(testBatch), new(testBatch)
- err := b1.Replay(p1)
- if err != nil {
- t.Fatal("error when replaying batch 1: ", err)
- }
- err = b2.Replay(p2)
- if err != nil {
- t.Fatal("error when replaying batch 2: ", err)
- }
- for i := range p1.rec {
- r1, r2 := p1.rec[i], p2.rec[i]
- if r1.kt != r2.kt {
- t.Errorf("invalid type on record '%d' want %d, got %d", i, r1.kt, r2.kt)
- }
- if !bytes.Equal(r1.key, r2.key) {
- t.Errorf("invalid key on record '%d' want %s, got %s", i, string(r1.key), string(r2.key))
- }
- if r1.kt == ktVal {
- if !bytes.Equal(r1.value, r2.value) {
- t.Errorf("invalid value on record '%d' want %s, got %s", i, string(r1.value), string(r2.value))
- }
- }
- }
-}
-
-func TestBatch_EncodeDecode(t *testing.T) {
- b1 := new(Batch)
- b1.seq = 10009
- b1.Put([]byte("key1"), []byte("value1"))
- b1.Put([]byte("key2"), []byte("value2"))
- b1.Delete([]byte("key1"))
- b1.Put([]byte("k"), []byte(""))
- b1.Put([]byte("zzzzzzzzzzz"), []byte("zzzzzzzzzzzzzzzzzzzzzzzz"))
- b1.Delete([]byte("key10000"))
- b1.Delete([]byte("k"))
- buf := b1.encode()
- b2 := new(Batch)
- err := b2.decode(0, buf)
- if err != nil {
- t.Error("error when decoding batch: ", err)
- }
- compareBatch(t, b1, b2)
-}
-
-func TestBatch_Append(t *testing.T) {
- b1 := new(Batch)
- b1.seq = 10009
- b1.Put([]byte("key1"), []byte("value1"))
- b1.Put([]byte("key2"), []byte("value2"))
- b1.Delete([]byte("key1"))
- b1.Put([]byte("foo"), []byte("foovalue"))
- b1.Put([]byte("bar"), []byte("barvalue"))
- b2a := new(Batch)
- b2a.seq = 10009
- b2a.Put([]byte("key1"), []byte("value1"))
- b2a.Put([]byte("key2"), []byte("value2"))
- b2a.Delete([]byte("key1"))
- b2b := new(Batch)
- b2b.Put([]byte("foo"), []byte("foovalue"))
- b2b.Put([]byte("bar"), []byte("barvalue"))
- b2a.append(b2b)
- compareBatch(t, b1, b2a)
-}
-
-func TestBatch_Size(t *testing.T) {
- b := new(Batch)
- for i := 0; i < 2; i++ {
- b.Put([]byte("key1"), []byte("value1"))
- b.Put([]byte("key2"), []byte("value2"))
- b.Delete([]byte("key1"))
- b.Put([]byte("foo"), []byte("foovalue"))
- b.Put([]byte("bar"), []byte("barvalue"))
- mem := memdb.New(&iComparer{comparer.DefaultComparer}, 0)
- b.memReplay(mem)
- if b.size() != mem.Size() {
- t.Errorf("invalid batch size calculation, want=%d got=%d", mem.Size(), b.size())
- }
- b.Reset()
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench2_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench2_test.go
deleted file mode 100644
index 0dd60fd82..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench2_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// +build !go1.2
-
-package leveldb
-
-import (
- "sync/atomic"
- "testing"
-)
-
-func BenchmarkDBReadConcurrent(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.gc()
- defer p.close()
-
- b.ResetTimer()
- b.SetBytes(116)
-
- b.RunParallel(func(pb *testing.PB) {
- iter := p.newIter()
- defer iter.Release()
- for pb.Next() && iter.Next() {
- }
- })
-}
-
-func BenchmarkDBReadConcurrent2(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.gc()
- defer p.close()
-
- b.ResetTimer()
- b.SetBytes(116)
-
- var dir uint32
- b.RunParallel(func(pb *testing.PB) {
- iter := p.newIter()
- defer iter.Release()
- if atomic.AddUint32(&dir, 1)%2 == 0 {
- for pb.Next() && iter.Next() {
- }
- } else {
- if pb.Next() && iter.Last() {
- for pb.Next() && iter.Prev() {
- }
- }
- }
- })
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench_test.go
deleted file mode 100644
index 91b426709..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/bench_test.go
+++ /dev/null
@@ -1,464 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- "bytes"
- "fmt"
- "math/rand"
- "os"
- "path/filepath"
- "runtime"
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/storage"
-)
-
-func randomString(r *rand.Rand, n int) []byte {
- b := new(bytes.Buffer)
- for i := 0; i < n; i++ {
- b.WriteByte(' ' + byte(r.Intn(95)))
- }
- return b.Bytes()
-}
-
-func compressibleStr(r *rand.Rand, frac float32, n int) []byte {
- nn := int(float32(n) * frac)
- rb := randomString(r, nn)
- b := make([]byte, 0, n+nn)
- for len(b) < n {
- b = append(b, rb...)
- }
- return b[:n]
-}
-
-type valueGen struct {
- src []byte
- pos int
-}
-
-func newValueGen(frac float32) *valueGen {
- v := new(valueGen)
- r := rand.New(rand.NewSource(301))
- v.src = make([]byte, 0, 1048576+100)
- for len(v.src) < 1048576 {
- v.src = append(v.src, compressibleStr(r, frac, 100)...)
- }
- return v
-}
-
-func (v *valueGen) get(n int) []byte {
- if v.pos+n > len(v.src) {
- v.pos = 0
- }
- v.pos += n
- return v.src[v.pos-n : v.pos]
-}
-
-var benchDB = filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbbench-%d", os.Getuid()))
-
-type dbBench struct {
- b *testing.B
- stor storage.Storage
- db *DB
-
- o *opt.Options
- ro *opt.ReadOptions
- wo *opt.WriteOptions
-
- keys, values [][]byte
-}
-
-func openDBBench(b *testing.B, noCompress bool) *dbBench {
- _, err := os.Stat(benchDB)
- if err == nil {
- err = os.RemoveAll(benchDB)
- if err != nil {
- b.Fatal("cannot remove old db: ", err)
- }
- }
-
- p := &dbBench{
- b: b,
- o: &opt.Options{},
- ro: &opt.ReadOptions{},
- wo: &opt.WriteOptions{},
- }
- p.stor, err = storage.OpenFile(benchDB)
- if err != nil {
- b.Fatal("cannot open stor: ", err)
- }
- if noCompress {
- p.o.Compression = opt.NoCompression
- }
-
- p.db, err = Open(p.stor, p.o)
- if err != nil {
- b.Fatal("cannot open db: ", err)
- }
-
- runtime.GOMAXPROCS(runtime.NumCPU())
- return p
-}
-
-func (p *dbBench) reopen() {
- p.db.Close()
- var err error
- p.db, err = Open(p.stor, p.o)
- if err != nil {
- p.b.Fatal("Reopen: got error: ", err)
- }
-}
-
-func (p *dbBench) populate(n int) {
- p.keys, p.values = make([][]byte, n), make([][]byte, n)
- v := newValueGen(0.5)
- for i := range p.keys {
- p.keys[i], p.values[i] = []byte(fmt.Sprintf("%016d", i)), v.get(100)
- }
-}
-
-func (p *dbBench) randomize() {
- m := len(p.keys)
- times := m * 2
- r1, r2 := rand.New(rand.NewSource(0xdeadbeef)), rand.New(rand.NewSource(0xbeefface))
- for n := 0; n < times; n++ {
- i, j := r1.Int()%m, r2.Int()%m
- if i == j {
- continue
- }
- p.keys[i], p.keys[j] = p.keys[j], p.keys[i]
- p.values[i], p.values[j] = p.values[j], p.values[i]
- }
-}
-
-func (p *dbBench) writes(perBatch int) {
- b := p.b
- db := p.db
-
- n := len(p.keys)
- m := n / perBatch
- if n%perBatch > 0 {
- m++
- }
- batches := make([]Batch, m)
- j := 0
- for i := range batches {
- first := true
- for ; j < n && ((j+1)%perBatch != 0 || first); j++ {
- first = false
- batches[i].Put(p.keys[j], p.values[j])
- }
- }
- runtime.GC()
-
- b.ResetTimer()
- b.StartTimer()
- for i := range batches {
- err := db.Write(&(batches[i]), p.wo)
- if err != nil {
- b.Fatal("write failed: ", err)
- }
- }
- b.StopTimer()
- b.SetBytes(116)
-}
-
-func (p *dbBench) gc() {
- p.keys, p.values = nil, nil
- runtime.GC()
-}
-
-func (p *dbBench) puts() {
- b := p.b
- db := p.db
-
- b.ResetTimer()
- b.StartTimer()
- for i := range p.keys {
- err := db.Put(p.keys[i], p.values[i], p.wo)
- if err != nil {
- b.Fatal("put failed: ", err)
- }
- }
- b.StopTimer()
- b.SetBytes(116)
-}
-
-func (p *dbBench) fill() {
- b := p.b
- db := p.db
-
- perBatch := 10000
- batch := new(Batch)
- for i, n := 0, len(p.keys); i < n; {
- first := true
- for ; i < n && ((i+1)%perBatch != 0 || first); i++ {
- first = false
- batch.Put(p.keys[i], p.values[i])
- }
- err := db.Write(batch, p.wo)
- if err != nil {
- b.Fatal("write failed: ", err)
- }
- batch.Reset()
- }
-}
-
-func (p *dbBench) gets() {
- b := p.b
- db := p.db
-
- b.ResetTimer()
- for i := range p.keys {
- _, err := db.Get(p.keys[i], p.ro)
- if err != nil {
- b.Error("got error: ", err)
- }
- }
- b.StopTimer()
-}
-
-func (p *dbBench) seeks() {
- b := p.b
-
- iter := p.newIter()
- defer iter.Release()
- b.ResetTimer()
- for i := range p.keys {
- if !iter.Seek(p.keys[i]) {
- b.Error("value not found for: ", string(p.keys[i]))
- }
- }
- b.StopTimer()
-}
-
-func (p *dbBench) newIter() iterator.Iterator {
- iter := p.db.NewIterator(nil, p.ro)
- err := iter.Error()
- if err != nil {
- p.b.Fatal("cannot create iterator: ", err)
- }
- return iter
-}
-
-func (p *dbBench) close() {
- if bp, err := p.db.GetProperty("leveldb.blockpool"); err == nil {
- p.b.Log("Block pool stats: ", bp)
- }
- p.db.Close()
- p.stor.Close()
- os.RemoveAll(benchDB)
- p.db = nil
- p.keys = nil
- p.values = nil
- runtime.GC()
- runtime.GOMAXPROCS(1)
-}
-
-func BenchmarkDBWrite(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.writes(1)
- p.close()
-}
-
-func BenchmarkDBWriteBatch(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.writes(1000)
- p.close()
-}
-
-func BenchmarkDBWriteUncompressed(b *testing.B) {
- p := openDBBench(b, true)
- p.populate(b.N)
- p.writes(1)
- p.close()
-}
-
-func BenchmarkDBWriteBatchUncompressed(b *testing.B) {
- p := openDBBench(b, true)
- p.populate(b.N)
- p.writes(1000)
- p.close()
-}
-
-func BenchmarkDBWriteRandom(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.randomize()
- p.writes(1)
- p.close()
-}
-
-func BenchmarkDBWriteRandomSync(b *testing.B) {
- p := openDBBench(b, false)
- p.wo.Sync = true
- p.populate(b.N)
- p.writes(1)
- p.close()
-}
-
-func BenchmarkDBOverwrite(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.writes(1)
- p.writes(1)
- p.close()
-}
-
-func BenchmarkDBOverwriteRandom(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.writes(1)
- p.randomize()
- p.writes(1)
- p.close()
-}
-
-func BenchmarkDBPut(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.puts()
- p.close()
-}
-
-func BenchmarkDBRead(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.gc()
-
- iter := p.newIter()
- b.ResetTimer()
- for iter.Next() {
- }
- iter.Release()
- b.StopTimer()
- b.SetBytes(116)
- p.close()
-}
-
-func BenchmarkDBReadGC(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
-
- iter := p.newIter()
- b.ResetTimer()
- for iter.Next() {
- }
- iter.Release()
- b.StopTimer()
- b.SetBytes(116)
- p.close()
-}
-
-func BenchmarkDBReadUncompressed(b *testing.B) {
- p := openDBBench(b, true)
- p.populate(b.N)
- p.fill()
- p.gc()
-
- iter := p.newIter()
- b.ResetTimer()
- for iter.Next() {
- }
- iter.Release()
- b.StopTimer()
- b.SetBytes(116)
- p.close()
-}
-
-func BenchmarkDBReadTable(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.reopen()
- p.gc()
-
- iter := p.newIter()
- b.ResetTimer()
- for iter.Next() {
- }
- iter.Release()
- b.StopTimer()
- b.SetBytes(116)
- p.close()
-}
-
-func BenchmarkDBReadReverse(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.gc()
-
- iter := p.newIter()
- b.ResetTimer()
- iter.Last()
- for iter.Prev() {
- }
- iter.Release()
- b.StopTimer()
- b.SetBytes(116)
- p.close()
-}
-
-func BenchmarkDBReadReverseTable(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.reopen()
- p.gc()
-
- iter := p.newIter()
- b.ResetTimer()
- iter.Last()
- for iter.Prev() {
- }
- iter.Release()
- b.StopTimer()
- b.SetBytes(116)
- p.close()
-}
-
-func BenchmarkDBSeek(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.seeks()
- p.close()
-}
-
-func BenchmarkDBSeekRandom(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.randomize()
- p.seeks()
- p.close()
-}
-
-func BenchmarkDBGet(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.gets()
- p.close()
-}
-
-func BenchmarkDBGetRandom(b *testing.B) {
- p := openDBBench(b, false)
- p.populate(b.N)
- p.fill()
- p.randomize()
- p.gets()
- p.close()
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/bench2_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/bench2_test.go
deleted file mode 100644
index 175e22203..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/bench2_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// +build !go1.2
-
-package cache
-
-import (
- "math/rand"
- "testing"
-)
-
-func BenchmarkLRUCache(b *testing.B) {
- c := NewCache(NewLRU(10000))
-
- b.SetParallelism(10)
- b.RunParallel(func(pb *testing.PB) {
- r := rand.New(rand.NewSource(time.Now().UnixNano()))
-
- for pb.Next() {
- key := uint64(r.Intn(1000000))
- c.Get(0, key, func() (int, Value) {
- return 1, key
- }).Release()
- }
- })
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go
deleted file mode 100644
index c2a50156f..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go
+++ /dev/null
@@ -1,554 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package cache
-
-import (
- "math/rand"
- "runtime"
- "sync"
- "sync/atomic"
- "testing"
- "time"
- "unsafe"
-)
-
-type int32o int32
-
-func (o *int32o) acquire() {
- if atomic.AddInt32((*int32)(o), 1) != 1 {
- panic("BUG: invalid ref")
- }
-}
-
-func (o *int32o) Release() {
- if atomic.AddInt32((*int32)(o), -1) != 0 {
- panic("BUG: invalid ref")
- }
-}
-
-type releaserFunc struct {
- fn func()
- value Value
-}
-
-func (r releaserFunc) Release() {
- if r.fn != nil {
- r.fn()
- }
-}
-
-func set(c *Cache, ns, key uint64, value Value, charge int, relf func()) *Handle {
- return c.Get(ns, key, func() (int, Value) {
- if relf != nil {
- return charge, releaserFunc{relf, value}
- } else {
- return charge, value
- }
- })
-}
-
-func TestCacheMap(t *testing.T) {
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- nsx := []struct {
- nobjects, nhandles, concurrent, repeat int
- }{
- {10000, 400, 50, 3},
- {100000, 1000, 100, 10},
- }
-
- var (
- objects [][]int32o
- handles [][]unsafe.Pointer
- )
-
- for _, x := range nsx {
- objects = append(objects, make([]int32o, x.nobjects))
- handles = append(handles, make([]unsafe.Pointer, x.nhandles))
- }
-
- c := NewCache(nil)
-
- wg := new(sync.WaitGroup)
- var done int32
-
- for ns, x := range nsx {
- for i := 0; i < x.concurrent; i++ {
- wg.Add(1)
- go func(ns, i, repeat int, objects []int32o, handles []unsafe.Pointer) {
- defer wg.Done()
- r := rand.New(rand.NewSource(time.Now().UnixNano()))
-
- for j := len(objects) * repeat; j >= 0; j-- {
- key := uint64(r.Intn(len(objects)))
- h := c.Get(uint64(ns), key, func() (int, Value) {
- o := &objects[key]
- o.acquire()
- return 1, o
- })
- if v := h.Value().(*int32o); v != &objects[key] {
- t.Fatalf("#%d invalid value: want=%p got=%p", ns, &objects[key], v)
- }
- if objects[key] != 1 {
- t.Fatalf("#%d invalid object %d: %d", ns, key, objects[key])
- }
- if !atomic.CompareAndSwapPointer(&handles[r.Intn(len(handles))], nil, unsafe.Pointer(h)) {
- h.Release()
- }
- }
- }(ns, i, x.repeat, objects[ns], handles[ns])
- }
-
- go func(handles []unsafe.Pointer) {
- r := rand.New(rand.NewSource(time.Now().UnixNano()))
-
- for atomic.LoadInt32(&done) == 0 {
- i := r.Intn(len(handles))
- h := (*Handle)(atomic.LoadPointer(&handles[i]))
- if h != nil && atomic.CompareAndSwapPointer(&handles[i], unsafe.Pointer(h), nil) {
- h.Release()
- }
- time.Sleep(time.Millisecond)
- }
- }(handles[ns])
- }
-
- go func() {
- handles := make([]*Handle, 100000)
- for atomic.LoadInt32(&done) == 0 {
- for i := range handles {
- handles[i] = c.Get(999999999, uint64(i), func() (int, Value) {
- return 1, 1
- })
- }
- for _, h := range handles {
- h.Release()
- }
- }
- }()
-
- wg.Wait()
-
- atomic.StoreInt32(&done, 1)
-
- for _, handles0 := range handles {
- for i := range handles0 {
- h := (*Handle)(atomic.LoadPointer(&handles0[i]))
- if h != nil && atomic.CompareAndSwapPointer(&handles0[i], unsafe.Pointer(h), nil) {
- h.Release()
- }
- }
- }
-
- for ns, objects0 := range objects {
- for i, o := range objects0 {
- if o != 0 {
- t.Fatalf("invalid object #%d.%d: ref=%d", ns, i, o)
- }
- }
- }
-}
-
-func TestCacheMap_NodesAndSize(t *testing.T) {
- c := NewCache(nil)
- if c.Nodes() != 0 {
- t.Errorf("invalid nodes counter: want=%d got=%d", 0, c.Nodes())
- }
- if c.Size() != 0 {
- t.Errorf("invalid size counter: want=%d got=%d", 0, c.Size())
- }
- set(c, 0, 1, 1, 1, nil)
- set(c, 0, 2, 2, 2, nil)
- set(c, 1, 1, 3, 3, nil)
- set(c, 2, 1, 4, 1, nil)
- if c.Nodes() != 4 {
- t.Errorf("invalid nodes counter: want=%d got=%d", 4, c.Nodes())
- }
- if c.Size() != 7 {
- t.Errorf("invalid size counter: want=%d got=%d", 4, c.Size())
- }
-}
-
-func TestLRUCache_Capacity(t *testing.T) {
- c := NewCache(NewLRU(10))
- if c.Capacity() != 10 {
- t.Errorf("invalid capacity: want=%d got=%d", 10, c.Capacity())
- }
- set(c, 0, 1, 1, 1, nil).Release()
- set(c, 0, 2, 2, 2, nil).Release()
- set(c, 1, 1, 3, 3, nil).Release()
- set(c, 2, 1, 4, 1, nil).Release()
- set(c, 2, 2, 5, 1, nil).Release()
- set(c, 2, 3, 6, 1, nil).Release()
- set(c, 2, 4, 7, 1, nil).Release()
- set(c, 2, 5, 8, 1, nil).Release()
- if c.Nodes() != 7 {
- t.Errorf("invalid nodes counter: want=%d got=%d", 7, c.Nodes())
- }
- if c.Size() != 10 {
- t.Errorf("invalid size counter: want=%d got=%d", 10, c.Size())
- }
- c.SetCapacity(9)
- if c.Capacity() != 9 {
- t.Errorf("invalid capacity: want=%d got=%d", 9, c.Capacity())
- }
- if c.Nodes() != 6 {
- t.Errorf("invalid nodes counter: want=%d got=%d", 6, c.Nodes())
- }
- if c.Size() != 8 {
- t.Errorf("invalid size counter: want=%d got=%d", 8, c.Size())
- }
-}
-
-func TestCacheMap_NilValue(t *testing.T) {
- c := NewCache(NewLRU(10))
- h := c.Get(0, 0, func() (size int, value Value) {
- return 1, nil
- })
- if h != nil {
- t.Error("cache handle is non-nil")
- }
- if c.Nodes() != 0 {
- t.Errorf("invalid nodes counter: want=%d got=%d", 0, c.Nodes())
- }
- if c.Size() != 0 {
- t.Errorf("invalid size counter: want=%d got=%d", 0, c.Size())
- }
-}
-
-func TestLRUCache_GetLatency(t *testing.T) {
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- const (
- concurrentSet = 30
- concurrentGet = 3
- duration = 3 * time.Second
- delay = 3 * time.Millisecond
- maxkey = 100000
- )
-
- var (
- set, getHit, getAll int32
- getMaxLatency, getDuration int64
- )
-
- c := NewCache(NewLRU(5000))
- wg := &sync.WaitGroup{}
- until := time.Now().Add(duration)
- for i := 0; i < concurrentSet; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- r := rand.New(rand.NewSource(time.Now().UnixNano()))
- for time.Now().Before(until) {
- c.Get(0, uint64(r.Intn(maxkey)), func() (int, Value) {
- time.Sleep(delay)
- atomic.AddInt32(&set, 1)
- return 1, 1
- }).Release()
- }
- }(i)
- }
- for i := 0; i < concurrentGet; i++ {
- wg.Add(1)
- go func(i int) {
- defer wg.Done()
- r := rand.New(rand.NewSource(time.Now().UnixNano()))
- for {
- mark := time.Now()
- if mark.Before(until) {
- h := c.Get(0, uint64(r.Intn(maxkey)), nil)
- latency := int64(time.Now().Sub(mark))
- m := atomic.LoadInt64(&getMaxLatency)
- if latency > m {
- atomic.CompareAndSwapInt64(&getMaxLatency, m, latency)
- }
- atomic.AddInt64(&getDuration, latency)
- if h != nil {
- atomic.AddInt32(&getHit, 1)
- h.Release()
- }
- atomic.AddInt32(&getAll, 1)
- } else {
- break
- }
- }
- }(i)
- }
-
- wg.Wait()
- getAvglatency := time.Duration(getDuration) / time.Duration(getAll)
- t.Logf("set=%d getHit=%d getAll=%d getMaxLatency=%v getAvgLatency=%v",
- set, getHit, getAll, time.Duration(getMaxLatency), getAvglatency)
-
- if getAvglatency > delay/3 {
- t.Errorf("get avg latency > %v: got=%v", delay/3, getAvglatency)
- }
-}
-
-func TestLRUCache_HitMiss(t *testing.T) {
- cases := []struct {
- key uint64
- value string
- }{
- {1, "vvvvvvvvv"},
- {100, "v1"},
- {0, "v2"},
- {12346, "v3"},
- {777, "v4"},
- {999, "v5"},
- {7654, "v6"},
- {2, "v7"},
- {3, "v8"},
- {9, "v9"},
- }
-
- setfin := 0
- c := NewCache(NewLRU(1000))
- for i, x := range cases {
- set(c, 0, x.key, x.value, len(x.value), func() {
- setfin++
- }).Release()
- for j, y := range cases {
- h := c.Get(0, y.key, nil)
- if j <= i {
- // should hit
- if h == nil {
- t.Errorf("case '%d' iteration '%d' is miss", i, j)
- } else {
- if x := h.Value().(releaserFunc).value.(string); x != y.value {
- t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value)
- }
- }
- } else {
- // should miss
- if h != nil {
- t.Errorf("case '%d' iteration '%d' is hit , value '%s'", i, j, h.Value().(releaserFunc).value.(string))
- }
- }
- if h != nil {
- h.Release()
- }
- }
- }
-
- for i, x := range cases {
- finalizerOk := false
- c.Delete(0, x.key, func() {
- finalizerOk = true
- })
-
- if !finalizerOk {
- t.Errorf("case %d delete finalizer not executed", i)
- }
-
- for j, y := range cases {
- h := c.Get(0, y.key, nil)
- if j > i {
- // should hit
- if h == nil {
- t.Errorf("case '%d' iteration '%d' is miss", i, j)
- } else {
- if x := h.Value().(releaserFunc).value.(string); x != y.value {
- t.Errorf("case '%d' iteration '%d' has invalid value got '%s', want '%s'", i, j, x, y.value)
- }
- }
- } else {
- // should miss
- if h != nil {
- t.Errorf("case '%d' iteration '%d' is hit, value '%s'", i, j, h.Value().(releaserFunc).value.(string))
- }
- }
- if h != nil {
- h.Release()
- }
- }
- }
-
- if setfin != len(cases) {
- t.Errorf("some set finalizer may not be executed, want=%d got=%d", len(cases), setfin)
- }
-}
-
-func TestLRUCache_Eviction(t *testing.T) {
- c := NewCache(NewLRU(12))
- o1 := set(c, 0, 1, 1, 1, nil)
- set(c, 0, 2, 2, 1, nil).Release()
- set(c, 0, 3, 3, 1, nil).Release()
- set(c, 0, 4, 4, 1, nil).Release()
- set(c, 0, 5, 5, 1, nil).Release()
- if h := c.Get(0, 2, nil); h != nil { // 1,3,4,5,2
- h.Release()
- }
- set(c, 0, 9, 9, 10, nil).Release() // 5,2,9
-
- for _, key := range []uint64{9, 2, 5, 1} {
- h := c.Get(0, key, nil)
- if h == nil {
- t.Errorf("miss for key '%d'", key)
- } else {
- if x := h.Value().(int); x != int(key) {
- t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
- }
- h.Release()
- }
- }
- o1.Release()
- for _, key := range []uint64{1, 2, 5} {
- h := c.Get(0, key, nil)
- if h == nil {
- t.Errorf("miss for key '%d'", key)
- } else {
- if x := h.Value().(int); x != int(key) {
- t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
- }
- h.Release()
- }
- }
- for _, key := range []uint64{3, 4, 9} {
- h := c.Get(0, key, nil)
- if h != nil {
- t.Errorf("hit for key '%d'", key)
- if x := h.Value().(int); x != int(key) {
- t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
- }
- h.Release()
- }
- }
-}
-
-func TestLRUCache_Evict(t *testing.T) {
- c := NewCache(NewLRU(6))
- set(c, 0, 1, 1, 1, nil).Release()
- set(c, 0, 2, 2, 1, nil).Release()
- set(c, 1, 1, 4, 1, nil).Release()
- set(c, 1, 2, 5, 1, nil).Release()
- set(c, 2, 1, 6, 1, nil).Release()
- set(c, 2, 2, 7, 1, nil).Release()
-
- for ns := 0; ns < 3; ns++ {
- for key := 1; key < 3; key++ {
- if h := c.Get(uint64(ns), uint64(key), nil); h != nil {
- h.Release()
- } else {
- t.Errorf("Cache.Get on #%d.%d return nil", ns, key)
- }
- }
- }
-
- if ok := c.Evict(0, 1); !ok {
- t.Error("first Cache.Evict on #0.1 return false")
- }
- if ok := c.Evict(0, 1); ok {
- t.Error("second Cache.Evict on #0.1 return true")
- }
- if h := c.Get(0, 1, nil); h != nil {
- t.Errorf("Cache.Get on #0.1 return non-nil: %v", h.Value())
- }
-
- c.EvictNS(1)
- if h := c.Get(1, 1, nil); h != nil {
- t.Errorf("Cache.Get on #1.1 return non-nil: %v", h.Value())
- }
- if h := c.Get(1, 2, nil); h != nil {
- t.Errorf("Cache.Get on #1.2 return non-nil: %v", h.Value())
- }
-
- c.EvictAll()
- for ns := 0; ns < 3; ns++ {
- for key := 1; key < 3; key++ {
- if h := c.Get(uint64(ns), uint64(key), nil); h != nil {
- t.Errorf("Cache.Get on #%d.%d return non-nil: %v", ns, key, h.Value())
- }
- }
- }
-}
-
-func TestLRUCache_Delete(t *testing.T) {
- delFuncCalled := 0
- delFunc := func() {
- delFuncCalled++
- }
-
- c := NewCache(NewLRU(2))
- set(c, 0, 1, 1, 1, nil).Release()
- set(c, 0, 2, 2, 1, nil).Release()
-
- if ok := c.Delete(0, 1, delFunc); !ok {
- t.Error("Cache.Delete on #1 return false")
- }
- if h := c.Get(0, 1, nil); h != nil {
- t.Errorf("Cache.Get on #1 return non-nil: %v", h.Value())
- }
- if ok := c.Delete(0, 1, delFunc); ok {
- t.Error("Cache.Delete on #1 return true")
- }
-
- h2 := c.Get(0, 2, nil)
- if h2 == nil {
- t.Error("Cache.Get on #2 return nil")
- }
- if ok := c.Delete(0, 2, delFunc); !ok {
- t.Error("(1) Cache.Delete on #2 return false")
- }
- if ok := c.Delete(0, 2, delFunc); !ok {
- t.Error("(2) Cache.Delete on #2 return false")
- }
-
- set(c, 0, 3, 3, 1, nil).Release()
- set(c, 0, 4, 4, 1, nil).Release()
- c.Get(0, 2, nil).Release()
-
- for key := 2; key <= 4; key++ {
- if h := c.Get(0, uint64(key), nil); h != nil {
- h.Release()
- } else {
- t.Errorf("Cache.Get on #%d return nil", key)
- }
- }
-
- h2.Release()
- if h := c.Get(0, 2, nil); h != nil {
- t.Errorf("Cache.Get on #2 return non-nil: %v", h.Value())
- }
-
- if delFuncCalled != 4 {
- t.Errorf("delFunc isn't called 4 times: got=%d", delFuncCalled)
- }
-}
-
-func TestLRUCache_Close(t *testing.T) {
- relFuncCalled := 0
- relFunc := func() {
- relFuncCalled++
- }
- delFuncCalled := 0
- delFunc := func() {
- delFuncCalled++
- }
-
- c := NewCache(NewLRU(2))
- set(c, 0, 1, 1, 1, relFunc).Release()
- set(c, 0, 2, 2, 1, relFunc).Release()
-
- h3 := set(c, 0, 3, 3, 1, relFunc)
- if h3 == nil {
- t.Error("Cache.Get on #3 return nil")
- }
- if ok := c.Delete(0, 3, delFunc); !ok {
- t.Error("Cache.Delete on #3 return false")
- }
-
- c.Close()
-
- if relFuncCalled != 3 {
- t.Errorf("relFunc isn't called 3 times: got=%d", relFuncCalled)
- }
- if delFuncCalled != 1 {
- t.Errorf("delFunc isn't called 1 times: got=%d", delFuncCalled)
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go
deleted file mode 100644
index a351874ed..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/corrupt_test.go
+++ /dev/null
@@ -1,500 +0,0 @@
-// Copyright (c) 2013, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- "bytes"
- "fmt"
- "github.com/syndtr/goleveldb/leveldb/filter"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/storage"
- "io"
- "math/rand"
- "testing"
-)
-
-const ctValSize = 1000
-
-type dbCorruptHarness struct {
- dbHarness
-}
-
-func newDbCorruptHarnessWopt(t *testing.T, o *opt.Options) *dbCorruptHarness {
- h := new(dbCorruptHarness)
- h.init(t, o)
- return h
-}
-
-func newDbCorruptHarness(t *testing.T) *dbCorruptHarness {
- return newDbCorruptHarnessWopt(t, &opt.Options{
- BlockCacheCapacity: 100,
- Strict: opt.StrictJournalChecksum,
- })
-}
-
-func (h *dbCorruptHarness) recover() {
- p := &h.dbHarness
- t := p.t
-
- var err error
- p.db, err = Recover(h.stor, h.o)
- if err != nil {
- t.Fatal("Repair: got error: ", err)
- }
-}
-
-func (h *dbCorruptHarness) build(n int) {
- p := &h.dbHarness
- t := p.t
- db := p.db
-
- batch := new(Batch)
- for i := 0; i < n; i++ {
- batch.Reset()
- batch.Put(tkey(i), tval(i, ctValSize))
- err := db.Write(batch, p.wo)
- if err != nil {
- t.Fatal("write error: ", err)
- }
- }
-}
-
-func (h *dbCorruptHarness) buildShuffled(n int, rnd *rand.Rand) {
- p := &h.dbHarness
- t := p.t
- db := p.db
-
- batch := new(Batch)
- for i := range rnd.Perm(n) {
- batch.Reset()
- batch.Put(tkey(i), tval(i, ctValSize))
- err := db.Write(batch, p.wo)
- if err != nil {
- t.Fatal("write error: ", err)
- }
- }
-}
-
-func (h *dbCorruptHarness) deleteRand(n, max int, rnd *rand.Rand) {
- p := &h.dbHarness
- t := p.t
- db := p.db
-
- batch := new(Batch)
- for i := 0; i < n; i++ {
- batch.Reset()
- batch.Delete(tkey(rnd.Intn(max)))
- err := db.Write(batch, p.wo)
- if err != nil {
- t.Fatal("write error: ", err)
- }
- }
-}
-
-func (h *dbCorruptHarness) corrupt(ft storage.FileType, fi, offset, n int) {
- p := &h.dbHarness
- t := p.t
-
- ff, _ := p.stor.GetFiles(ft)
- sff := files(ff)
- sff.sort()
- if fi < 0 {
- fi = len(sff) - 1
- }
- if fi >= len(sff) {
- t.Fatalf("no such file with type %q with index %d", ft, fi)
- }
-
- file := sff[fi]
-
- r, err := file.Open()
- if err != nil {
- t.Fatal("cannot open file: ", err)
- }
- x, err := r.Seek(0, 2)
- if err != nil {
- t.Fatal("cannot query file size: ", err)
- }
- m := int(x)
- if _, err := r.Seek(0, 0); err != nil {
- t.Fatal(err)
- }
-
- if offset < 0 {
- if -offset > m {
- offset = 0
- } else {
- offset = m + offset
- }
- }
- if offset > m {
- offset = m
- }
- if offset+n > m {
- n = m - offset
- }
-
- buf := make([]byte, m)
- _, err = io.ReadFull(r, buf)
- if err != nil {
- t.Fatal("cannot read file: ", err)
- }
- r.Close()
-
- for i := 0; i < n; i++ {
- buf[offset+i] ^= 0x80
- }
-
- err = file.Remove()
- if err != nil {
- t.Fatal("cannot remove old file: ", err)
- }
- w, err := file.Create()
- if err != nil {
- t.Fatal("cannot create new file: ", err)
- }
- _, err = w.Write(buf)
- if err != nil {
- t.Fatal("cannot write new file: ", err)
- }
- w.Close()
-}
-
-func (h *dbCorruptHarness) removeAll(ft storage.FileType) {
- ff, err := h.stor.GetFiles(ft)
- if err != nil {
- h.t.Fatal("get files: ", err)
- }
- for _, f := range ff {
- if err := f.Remove(); err != nil {
- h.t.Error("remove file: ", err)
- }
- }
-}
-
-func (h *dbCorruptHarness) removeOne(ft storage.FileType) {
- ff, err := h.stor.GetFiles(ft)
- if err != nil {
- h.t.Fatal("get files: ", err)
- }
- f := ff[rand.Intn(len(ff))]
- h.t.Logf("removing file @%d", f.Num())
- if err := f.Remove(); err != nil {
- h.t.Error("remove file: ", err)
- }
-}
-
-func (h *dbCorruptHarness) check(min, max int) {
- p := &h.dbHarness
- t := p.t
- db := p.db
-
- var n, badk, badv, missed, good int
- iter := db.NewIterator(nil, p.ro)
- for iter.Next() {
- k := 0
- fmt.Sscanf(string(iter.Key()), "%d", &k)
- if k < n {
- badk++
- continue
- }
- missed += k - n
- n = k + 1
- if !bytes.Equal(iter.Value(), tval(k, ctValSize)) {
- badv++
- } else {
- good++
- }
- }
- err := iter.Error()
- iter.Release()
- t.Logf("want=%d..%d got=%d badkeys=%d badvalues=%d missed=%d, err=%v",
- min, max, good, badk, badv, missed, err)
- if good < min || good > max {
- t.Errorf("good entries number not in range")
- }
-}
-
-func TestCorruptDB_Journal(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.build(100)
- h.check(100, 100)
- h.closeDB()
- h.corrupt(storage.TypeJournal, -1, 19, 1)
- h.corrupt(storage.TypeJournal, -1, 32*1024+1000, 1)
-
- h.openDB()
- h.check(36, 36)
-
- h.close()
-}
-
-func TestCorruptDB_Table(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.build(100)
- h.compactMem()
- h.compactRangeAt(0, "", "")
- h.compactRangeAt(1, "", "")
- h.closeDB()
- h.corrupt(storage.TypeTable, -1, 100, 1)
-
- h.openDB()
- h.check(99, 99)
-
- h.close()
-}
-
-func TestCorruptDB_TableIndex(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.build(10000)
- h.compactMem()
- h.closeDB()
- h.corrupt(storage.TypeTable, -1, -2000, 500)
-
- h.openDB()
- h.check(5000, 9999)
-
- h.close()
-}
-
-func TestCorruptDB_MissingManifest(t *testing.T) {
- rnd := rand.New(rand.NewSource(0x0badda7a))
- h := newDbCorruptHarnessWopt(t, &opt.Options{
- BlockCacheCapacity: 100,
- Strict: opt.StrictJournalChecksum,
- WriteBuffer: 1000 * 60,
- })
-
- h.build(1000)
- h.compactMem()
- h.buildShuffled(1000, rnd)
- h.compactMem()
- h.deleteRand(500, 1000, rnd)
- h.compactMem()
- h.buildShuffled(1000, rnd)
- h.compactMem()
- h.deleteRand(500, 1000, rnd)
- h.compactMem()
- h.buildShuffled(1000, rnd)
- h.compactMem()
- h.closeDB()
-
- h.stor.SetIgnoreOpenErr(storage.TypeManifest)
- h.removeAll(storage.TypeManifest)
- h.openAssert(false)
- h.stor.SetIgnoreOpenErr(0)
-
- h.recover()
- h.check(1000, 1000)
- h.build(1000)
- h.compactMem()
- h.compactRange("", "")
- h.closeDB()
-
- h.recover()
- h.check(1000, 1000)
-
- h.close()
-}
-
-func TestCorruptDB_SequenceNumberRecovery(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.put("foo", "v1")
- h.put("foo", "v2")
- h.put("foo", "v3")
- h.put("foo", "v4")
- h.put("foo", "v5")
- h.closeDB()
-
- h.recover()
- h.getVal("foo", "v5")
- h.put("foo", "v6")
- h.getVal("foo", "v6")
-
- h.reopenDB()
- h.getVal("foo", "v6")
-
- h.close()
-}
-
-func TestCorruptDB_SequenceNumberRecoveryTable(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.put("foo", "v1")
- h.put("foo", "v2")
- h.put("foo", "v3")
- h.compactMem()
- h.put("foo", "v4")
- h.put("foo", "v5")
- h.compactMem()
- h.closeDB()
-
- h.recover()
- h.getVal("foo", "v5")
- h.put("foo", "v6")
- h.getVal("foo", "v6")
-
- h.reopenDB()
- h.getVal("foo", "v6")
-
- h.close()
-}
-
-func TestCorruptDB_CorruptedManifest(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.put("foo", "hello")
- h.compactMem()
- h.compactRange("", "")
- h.closeDB()
- h.corrupt(storage.TypeManifest, -1, 0, 1000)
- h.openAssert(false)
-
- h.recover()
- h.getVal("foo", "hello")
-
- h.close()
-}
-
-func TestCorruptDB_CompactionInputError(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.build(10)
- h.compactMem()
- h.closeDB()
- h.corrupt(storage.TypeTable, -1, 100, 1)
-
- h.openDB()
- h.check(9, 9)
-
- h.build(10000)
- h.check(10000, 10000)
-
- h.close()
-}
-
-func TestCorruptDB_UnrelatedKeys(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.build(10)
- h.compactMem()
- h.closeDB()
- h.corrupt(storage.TypeTable, -1, 100, 1)
-
- h.openDB()
- h.put(string(tkey(1000)), string(tval(1000, ctValSize)))
- h.getVal(string(tkey(1000)), string(tval(1000, ctValSize)))
- h.compactMem()
- h.getVal(string(tkey(1000)), string(tval(1000, ctValSize)))
-
- h.close()
-}
-
-func TestCorruptDB_Level0NewerFileHasOlderSeqnum(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.put("a", "v1")
- h.put("b", "v1")
- h.compactMem()
- h.put("a", "v2")
- h.put("b", "v2")
- h.compactMem()
- h.put("a", "v3")
- h.put("b", "v3")
- h.compactMem()
- h.put("c", "v0")
- h.put("d", "v0")
- h.compactMem()
- h.compactRangeAt(1, "", "")
- h.closeDB()
-
- h.recover()
- h.getVal("a", "v3")
- h.getVal("b", "v3")
- h.getVal("c", "v0")
- h.getVal("d", "v0")
-
- h.close()
-}
-
-func TestCorruptDB_RecoverInvalidSeq_Issue53(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.put("a", "v1")
- h.put("b", "v1")
- h.compactMem()
- h.put("a", "v2")
- h.put("b", "v2")
- h.compactMem()
- h.put("a", "v3")
- h.put("b", "v3")
- h.compactMem()
- h.put("c", "v0")
- h.put("d", "v0")
- h.compactMem()
- h.compactRangeAt(0, "", "")
- h.closeDB()
-
- h.recover()
- h.getVal("a", "v3")
- h.getVal("b", "v3")
- h.getVal("c", "v0")
- h.getVal("d", "v0")
-
- h.close()
-}
-
-func TestCorruptDB_MissingTableFiles(t *testing.T) {
- h := newDbCorruptHarness(t)
-
- h.put("a", "v1")
- h.put("b", "v1")
- h.compactMem()
- h.put("c", "v2")
- h.put("d", "v2")
- h.compactMem()
- h.put("e", "v3")
- h.put("f", "v3")
- h.closeDB()
-
- h.removeOne(storage.TypeTable)
- h.openAssert(false)
-
- h.close()
-}
-
-func TestCorruptDB_RecoverTable(t *testing.T) {
- h := newDbCorruptHarnessWopt(t, &opt.Options{
- WriteBuffer: 112 * opt.KiB,
- CompactionTableSize: 90 * opt.KiB,
- Filter: filter.NewBloomFilter(10),
- })
-
- h.build(1000)
- h.compactMem()
- h.compactRangeAt(0, "", "")
- h.compactRangeAt(1, "", "")
- seq := h.db.seq
- h.closeDB()
- h.corrupt(storage.TypeTable, 0, 1000, 1)
- h.corrupt(storage.TypeTable, 3, 10000, 1)
- // Corrupted filter shouldn't affect recovery.
- h.corrupt(storage.TypeTable, 3, 113888, 10)
- h.corrupt(storage.TypeTable, -1, 20000, 1)
-
- h.recover()
- if h.db.seq != seq {
- t.Errorf("invalid seq, want=%d got=%d", seq, h.db.seq)
- }
- h.check(985, 985)
-
- h.close()
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go
index 323353b2a..537addb62 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db.go
@@ -36,14 +36,14 @@ type DB struct {
s *session
// MemDB.
- memMu sync.RWMutex
- memPool chan *memdb.DB
- mem, frozenMem *memDB
- journal *journal.Writer
- journalWriter storage.Writer
- journalFile storage.File
- frozenJournalFile storage.File
- frozenSeq uint64
+ memMu sync.RWMutex
+ memPool chan *memdb.DB
+ mem, frozenMem *memDB
+ journal *journal.Writer
+ journalWriter storage.Writer
+ journalFd storage.FileDesc
+ frozenJournalFd storage.FileDesc
+ frozenSeq uint64
// Snapshot.
snapsMu sync.Mutex
@@ -61,15 +61,19 @@ type DB struct {
writeDelayN int
journalC chan *Batch
journalAckC chan error
+ tr *Transaction
// Compaction.
- tcompCmdC chan cCmd
- tcompPauseC chan chan<- struct{}
- mcompCmdC chan cCmd
- compErrC chan error
- compPerErrC chan error
- compErrSetC chan error
- compStats []cStats
+ compCommitLk sync.Mutex
+ tcompCmdC chan cCmd
+ tcompPauseC chan chan<- struct{}
+ mcompCmdC chan cCmd
+ compErrC chan error
+ compPerErrC chan error
+ compErrSetC chan error
+ compWriteLocking bool
+ compStats cStats
+ memdbMaxLevel int // For testing.
// Close.
closeW sync.WaitGroup
@@ -103,33 +107,48 @@ func openDB(s *session) (*DB, error) {
compErrC: make(chan error),
compPerErrC: make(chan error),
compErrSetC: make(chan error),
- compStats: make([]cStats, s.o.GetNumLevel()),
// Close
closeC: make(chan struct{}),
}
- if err := db.recoverJournal(); err != nil {
- return nil, err
- }
+ // Read-only mode.
+ readOnly := s.o.GetReadOnly()
- // Remove any obsolete files.
- if err := db.checkAndCleanFiles(); err != nil {
- // Close journal.
- if db.journal != nil {
- db.journal.Close()
- db.journalWriter.Close()
+ if readOnly {
+ // Recover journals (read-only mode).
+ if err := db.recoverJournalRO(); err != nil {
+ return nil, err
}
- return nil, err
+ } else {
+ // Recover journals.
+ if err := db.recoverJournal(); err != nil {
+ return nil, err
+ }
+
+ // Remove any obsolete files.
+ if err := db.checkAndCleanFiles(); err != nil {
+ // Close journal.
+ if db.journal != nil {
+ db.journal.Close()
+ db.journalWriter.Close()
+ }
+ return nil, err
+ }
+
}
// Doesn't need to be included in the wait group.
go db.compactionError()
go db.mpoolDrain()
- db.closeW.Add(3)
- go db.tCompaction()
- go db.mCompaction()
- go db.jWriter()
+ if readOnly {
+ db.SetReadOnly()
+ } else {
+ db.closeW.Add(3)
+ go db.tCompaction()
+ go db.mCompaction()
+ go db.jWriter()
+ }
s.logf("db@open done T·%v", time.Since(start))
@@ -192,7 +211,7 @@ func Open(stor storage.Storage, o *opt.Options) (db *DB, err error) {
// The returned DB instance is goroutine-safe.
// The DB must be closed after use, by calling Close method.
func OpenFile(path string, o *opt.Options) (db *DB, err error) {
- stor, err := storage.OpenFile(path)
+ stor, err := storage.OpenFile(path, o.GetReadOnly())
if err != nil {
return
}
@@ -242,7 +261,7 @@ func Recover(stor storage.Storage, o *opt.Options) (db *DB, err error) {
// The returned DB instance is goroutine-safe.
// The DB must be closed after use, by calling Close method.
func RecoverFile(path string, o *opt.Options) (db *DB, err error) {
- stor, err := storage.OpenFile(path)
+ stor, err := storage.OpenFile(path, false)
if err != nil {
return
}
@@ -261,12 +280,11 @@ func recoverTable(s *session, o *opt.Options) error {
o.Strict &= ^opt.StrictReader
// Get all tables and sort it by file number.
- tableFiles_, err := s.getFiles(storage.TypeTable)
+ fds, err := s.stor.List(storage.TypeTable)
if err != nil {
return err
}
- tableFiles := files(tableFiles_)
- tableFiles.sort()
+ sortFds(fds)
var (
maxSeq uint64
@@ -274,21 +292,22 @@ func recoverTable(s *session, o *opt.Options) error {
// We will drop corrupted table.
strict = o.GetStrict(opt.StrictRecovery)
+ noSync = o.GetNoSync()
- rec = &sessionRecord{numLevel: o.GetNumLevel()}
+ rec = &sessionRecord{}
bpool = util.NewBufferPool(o.GetBlockSize() + 5)
)
- buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) {
- tmp = s.newTemp()
- writer, err := tmp.Create()
+ buildTable := func(iter iterator.Iterator) (tmpFd storage.FileDesc, size int64, err error) {
+ tmpFd = s.newTemp()
+ writer, err := s.stor.Create(tmpFd)
if err != nil {
return
}
defer func() {
writer.Close()
if err != nil {
- tmp.Remove()
- tmp = nil
+ s.stor.Remove(tmpFd)
+ tmpFd = storage.FileDesc{}
}
}()
@@ -311,16 +330,18 @@ func recoverTable(s *session, o *opt.Options) error {
if err != nil {
return
}
- err = writer.Sync()
- if err != nil {
- return
+ if !noSync {
+ err = writer.Sync()
+ if err != nil {
+ return
+ }
}
size = int64(tw.BytesLen())
return
}
- recoverTable := func(file storage.File) error {
- s.logf("table@recovery recovering @%d", file.Num())
- reader, err := file.Open()
+ recoverTable := func(fd storage.FileDesc) error {
+ s.logf("table@recovery recovering @%d", fd.Num)
+ reader, err := s.stor.Open(fd)
if err != nil {
return err
}
@@ -342,7 +363,7 @@ func recoverTable(s *session, o *opt.Options) error {
tgoodKey, tcorruptedKey, tcorruptedBlock int
imin, imax []byte
)
- tr, err := table.NewReader(reader, size, storage.NewFileInfo(file), nil, bpool, o)
+ tr, err := table.NewReader(reader, size, fd, nil, bpool, o)
if err != nil {
return err
}
@@ -350,7 +371,7 @@ func recoverTable(s *session, o *opt.Options) error {
if itererr, ok := iter.(iterator.ErrorCallbackSetter); ok {
itererr.SetErrorCallback(func(err error) {
if errors.IsCorrupted(err) {
- s.logf("table@recovery block corruption @%d %q", file.Num(), err)
+ s.logf("table@recovery block corruption @%d %q", fd.Num, err)
tcorruptedBlock++
}
})
@@ -385,23 +406,23 @@ func recoverTable(s *session, o *opt.Options) error {
if strict && (tcorruptedKey > 0 || tcorruptedBlock > 0) {
droppedTable++
- s.logf("table@recovery dropped @%d Gk·%d Ck·%d Cb·%d S·%d Q·%d", file.Num(), tgoodKey, tcorruptedKey, tcorruptedBlock, size, tSeq)
+ s.logf("table@recovery dropped @%d Gk·%d Ck·%d Cb·%d S·%d Q·%d", fd.Num, tgoodKey, tcorruptedKey, tcorruptedBlock, size, tSeq)
return nil
}
if tgoodKey > 0 {
if tcorruptedKey > 0 || tcorruptedBlock > 0 {
// Rebuild the table.
- s.logf("table@recovery rebuilding @%d", file.Num())
+ s.logf("table@recovery rebuilding @%d", fd.Num)
iter := tr.NewIterator(nil, nil)
- tmp, newSize, err := buildTable(iter)
+ tmpFd, newSize, err := buildTable(iter)
iter.Release()
if err != nil {
return err
}
closed = true
reader.Close()
- if err := file.Replace(tmp); err != nil {
+ if err := s.stor.Rename(tmpFd, fd); err != nil {
return err
}
size = newSize
@@ -411,30 +432,30 @@ func recoverTable(s *session, o *opt.Options) error {
}
recoveredKey += tgoodKey
// Add table to level 0.
- rec.addTable(0, file.Num(), uint64(size), imin, imax)
- s.logf("table@recovery recovered @%d Gk·%d Ck·%d Cb·%d S·%d Q·%d", file.Num(), tgoodKey, tcorruptedKey, tcorruptedBlock, size, tSeq)
+ rec.addTable(0, fd.Num, size, imin, imax)
+ s.logf("table@recovery recovered @%d Gk·%d Ck·%d Cb·%d S·%d Q·%d", fd.Num, tgoodKey, tcorruptedKey, tcorruptedBlock, size, tSeq)
} else {
droppedTable++
- s.logf("table@recovery unrecoverable @%d Ck·%d Cb·%d S·%d", file.Num(), tcorruptedKey, tcorruptedBlock, size)
+ s.logf("table@recovery unrecoverable @%d Ck·%d Cb·%d S·%d", fd.Num, tcorruptedKey, tcorruptedBlock, size)
}
return nil
}
// Recover all tables.
- if len(tableFiles) > 0 {
- s.logf("table@recovery F·%d", len(tableFiles))
+ if len(fds) > 0 {
+ s.logf("table@recovery F·%d", len(fds))
// Mark file number as used.
- s.markFileNum(tableFiles[len(tableFiles)-1].Num())
+ s.markFileNum(fds[len(fds)-1].Num)
- for _, file := range tableFiles {
- if err := recoverTable(file); err != nil {
+ for _, fd := range fds {
+ if err := recoverTable(fd); err != nil {
return err
}
}
- s.logf("table@recovery recovered F·%d N·%d Gk·%d Ck·%d Q·%d", len(tableFiles), recoveredKey, goodKey, corruptedKey, maxSeq)
+ s.logf("table@recovery recovered F·%d N·%d Gk·%d Ck·%d Q·%d", len(fds), recoveredKey, goodKey, corruptedKey, maxSeq)
}
// Set sequence number.
@@ -450,132 +471,136 @@ func recoverTable(s *session, o *opt.Options) error {
}
func (db *DB) recoverJournal() error {
- // Get all tables and sort it by file number.
- journalFiles_, err := db.s.getFiles(storage.TypeJournal)
+ // Get all journals and sort it by file number.
+ fds_, err := db.s.stor.List(storage.TypeJournal)
if err != nil {
return err
}
- journalFiles := files(journalFiles_)
- journalFiles.sort()
+ sortFds(fds_)
- // Discard older journal.
- prev := -1
- for i, file := range journalFiles {
- if file.Num() >= db.s.stJournalNum {
- if prev >= 0 {
- i--
- journalFiles[i] = journalFiles[prev]
- }
- journalFiles = journalFiles[i:]
- break
- } else if file.Num() == db.s.stPrevJournalNum {
- prev = i
- }
- }
-
- var jr *journal.Reader
- var of storage.File
- var mem *memdb.DB
- batch := new(Batch)
- cm := newCMem(db.s)
- buf := new(util.Buffer)
- // Options.
- strict := db.s.o.GetStrict(opt.StrictJournal)
- checksum := db.s.o.GetStrict(opt.StrictJournalChecksum)
- writeBuffer := db.s.o.GetWriteBuffer()
- recoverJournal := func(file storage.File) error {
- db.logf("journal@recovery recovering @%d", file.Num())
- reader, err := file.Open()
- if err != nil {
- return err
+ // Journals that will be recovered.
+ var fds []storage.FileDesc
+ for _, fd := range fds_ {
+ if fd.Num >= db.s.stJournalNum || fd.Num == db.s.stPrevJournalNum {
+ fds = append(fds, fd)
}
- defer reader.Close()
+ }
- // Create/reset journal reader instance.
- if jr == nil {
- jr = journal.NewReader(reader, dropper{db.s, file}, strict, checksum)
- } else {
- jr.Reset(reader, dropper{db.s, file}, strict, checksum)
- }
+ var (
+ ofd storage.FileDesc // Obsolete file.
+ rec = &sessionRecord{}
+ )
- // Flush memdb and remove obsolete journal file.
- if of != nil {
- if mem.Len() > 0 {
- if err := cm.flush(mem, 0); err != nil {
- return err
- }
- }
- if err := cm.commit(file.Num(), db.seq); err != nil {
+ // Recover journals.
+ if len(fds) > 0 {
+ db.logf("journal@recovery F·%d", len(fds))
+
+ // Mark file number as used.
+ db.s.markFileNum(fds[len(fds)-1].Num)
+
+ var (
+ // Options.
+ strict = db.s.o.GetStrict(opt.StrictJournal)
+ checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
+ writeBuffer = db.s.o.GetWriteBuffer()
+
+ jr *journal.Reader
+ mdb = memdb.New(db.s.icmp, writeBuffer)
+ buf = &util.Buffer{}
+ batch = &Batch{}
+ )
+
+ for _, fd := range fds {
+ db.logf("journal@recovery recovering @%d", fd.Num)
+
+ fr, err := db.s.stor.Open(fd)
+ if err != nil {
return err
}
- cm.reset()
- of.Remove()
- of = nil
- }
- // Replay journal to memdb.
- mem.Reset()
- for {
- r, err := jr.Next()
- if err != nil {
- if err == io.EOF {
- break
- }
- return errors.SetFile(err, file)
+ // Create or reset journal reader instance.
+ if jr == nil {
+ jr = journal.NewReader(fr, dropper{db.s, fd}, strict, checksum)
+ } else {
+ jr.Reset(fr, dropper{db.s, fd}, strict, checksum)
}
- buf.Reset()
- if _, err := buf.ReadFrom(r); err != nil {
- if err == io.ErrUnexpectedEOF {
- // This is error returned due to corruption, with strict == false.
- continue
- } else {
- return errors.SetFile(err, file)
+ // Flush memdb and remove obsolete journal file.
+ if !ofd.Nil() {
+ if mdb.Len() > 0 {
+ if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
+ fr.Close()
+ return err
+ }
}
- }
- if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mem); err != nil {
- if strict || !errors.IsCorrupted(err) {
- return errors.SetFile(err, file)
- } else {
- db.s.logf("journal error: %v (skipped)", err)
- // We won't apply sequence number as it might be corrupted.
- continue
+
+ rec.setJournalNum(fd.Num)
+ rec.setSeqNum(db.seq)
+ if err := db.s.commit(rec); err != nil {
+ fr.Close()
+ return err
}
+ rec.resetAddedTables()
+
+ db.s.stor.Remove(ofd)
+ ofd = storage.FileDesc{}
}
- // Save sequence number.
- db.seq = batch.seq + uint64(batch.Len())
+ // Replay journal to memdb.
+ mdb.Reset()
+ for {
+ r, err := jr.Next()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
- // Flush it if large enough.
- if mem.Size() >= writeBuffer {
- if err := cm.flush(mem, 0); err != nil {
- return err
+ fr.Close()
+ return errors.SetFd(err, fd)
}
- mem.Reset()
- }
- }
- of = file
- return nil
- }
+ buf.Reset()
+ if _, err := buf.ReadFrom(r); err != nil {
+ if err == io.ErrUnexpectedEOF {
+ // This is error returned due to corruption, with strict == false.
+ continue
+ }
+
+ fr.Close()
+ return errors.SetFd(err, fd)
+ }
+ if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
+ if !strict && errors.IsCorrupted(err) {
+ db.s.logf("journal error: %v (skipped)", err)
+ // We won't apply sequence number as it might be corrupted.
+ continue
+ }
+
+ fr.Close()
+ return errors.SetFd(err, fd)
+ }
- // Recover all journals.
- if len(journalFiles) > 0 {
- db.logf("journal@recovery F·%d", len(journalFiles))
+ // Save sequence number.
+ db.seq = batch.seq + uint64(batch.Len())
- // Mark file number as used.
- db.s.markFileNum(journalFiles[len(journalFiles)-1].Num())
+ // Flush it if large enough.
+ if mdb.Size() >= writeBuffer {
+ if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
+ fr.Close()
+ return err
+ }
- mem = memdb.New(db.s.icmp, writeBuffer)
- for _, file := range journalFiles {
- if err := recoverJournal(file); err != nil {
- return err
+ mdb.Reset()
+ }
}
+
+ fr.Close()
+ ofd = fd
}
- // Flush the last journal.
- if mem.Len() > 0 {
- if err := cm.flush(mem, 0); err != nil {
+ // Flush the last memdb.
+ if mdb.Len() > 0 {
+ if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
return err
}
}
@@ -587,8 +612,10 @@ func (db *DB) recoverJournal() error {
}
// Commit.
- if err := cm.commit(db.journalFile.Num(), db.seq); err != nil {
- // Close journal.
+ rec.setJournalNum(db.journalFd.Num)
+ rec.setSeqNum(db.seq)
+ if err := db.s.commit(rec); err != nil {
+ // Close journal on error.
if db.journal != nil {
db.journal.Close()
db.journalWriter.Close()
@@ -597,15 +624,139 @@ func (db *DB) recoverJournal() error {
}
// Remove the last obsolete journal file.
- if of != nil {
- of.Remove()
+ if !ofd.Nil() {
+ db.s.stor.Remove(ofd)
+ }
+
+ return nil
+}
+
+func (db *DB) recoverJournalRO() error {
+ // Get all journals and sort it by file number.
+ fds_, err := db.s.stor.List(storage.TypeJournal)
+ if err != nil {
+ return err
+ }
+ sortFds(fds_)
+
+ // Journals that will be recovered.
+ var fds []storage.FileDesc
+ for _, fd := range fds_ {
+ if fd.Num >= db.s.stJournalNum || fd.Num == db.s.stPrevJournalNum {
+ fds = append(fds, fd)
+ }
+ }
+
+ var (
+ // Options.
+ strict = db.s.o.GetStrict(opt.StrictJournal)
+ checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
+ writeBuffer = db.s.o.GetWriteBuffer()
+
+ mdb = memdb.New(db.s.icmp, writeBuffer)
+ )
+
+ // Recover journals.
+ if len(fds) > 0 {
+ db.logf("journal@recovery RO·Mode F·%d", len(fds))
+
+ var (
+ jr *journal.Reader
+ buf = &util.Buffer{}
+ batch = &Batch{}
+ )
+
+ for _, fd := range fds {
+ db.logf("journal@recovery recovering @%d", fd.Num)
+
+ fr, err := db.s.stor.Open(fd)
+ if err != nil {
+ return err
+ }
+
+ // Create or reset journal reader instance.
+ if jr == nil {
+ jr = journal.NewReader(fr, dropper{db.s, fd}, strict, checksum)
+ } else {
+ jr.Reset(fr, dropper{db.s, fd}, strict, checksum)
+ }
+
+ // Replay journal to memdb.
+ for {
+ r, err := jr.Next()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+
+ fr.Close()
+ return errors.SetFd(err, fd)
+ }
+
+ buf.Reset()
+ if _, err := buf.ReadFrom(r); err != nil {
+ if err == io.ErrUnexpectedEOF {
+ // This is error returned due to corruption, with strict == false.
+ continue
+ }
+
+ fr.Close()
+ return errors.SetFd(err, fd)
+ }
+ if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
+ if !strict && errors.IsCorrupted(err) {
+ db.s.logf("journal error: %v (skipped)", err)
+ // We won't apply sequence number as it might be corrupted.
+ continue
+ }
+
+ fr.Close()
+ return errors.SetFd(err, fd)
+ }
+
+ // Save sequence number.
+ db.seq = batch.seq + uint64(batch.Len())
+ }
+
+ fr.Close()
+ }
}
+ // Set memDB.
+ db.mem = &memDB{db: db, DB: mdb, ref: 1}
+
return nil
}
-func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
- ikey := newIkey(key, seq, ktSeek)
+func memGet(mdb *memdb.DB, ikey iKey, icmp *iComparer) (ok bool, mv []byte, err error) {
+ mk, mv, err := mdb.Find(ikey)
+ if err == nil {
+ ukey, _, kt, kerr := parseIkey(mk)
+ if kerr != nil {
+ // Shouldn't have had happen.
+ panic(kerr)
+ }
+ if icmp.uCompare(ukey, ikey.ukey()) == 0 {
+ if kt == ktDel {
+ return true, nil, ErrNotFound
+ }
+ return true, mv, nil
+
+ }
+ } else if err != ErrNotFound {
+ return true, nil, err
+ }
+ return
+}
+
+func (db *DB) get(auxm *memdb.DB, auxt tFiles, key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
+ ikey := makeIkey(nil, key, seq, ktSeek)
+
+ if auxm != nil {
+ if ok, mv, me := memGet(auxm, ikey, db.s.icmp); ok {
+ return append([]byte{}, mv...), me
+ }
+ }
em, fm := db.getMems()
for _, m := range [...]*memDB{em, fm} {
@@ -614,36 +765,36 @@ func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, er
}
defer m.decref()
- mk, mv, me := m.mdb.Find(ikey)
- if me == nil {
- ukey, _, kt, kerr := parseIkey(mk)
- if kerr != nil {
- // Shouldn't have had happen.
- panic(kerr)
- }
- if db.s.icmp.uCompare(ukey, key) == 0 {
- if kt == ktDel {
- return nil, ErrNotFound
- }
- return append([]byte{}, mv...), nil
- }
- } else if me != ErrNotFound {
- return nil, me
+ if ok, mv, me := memGet(m.DB, ikey, db.s.icmp); ok {
+ return append([]byte{}, mv...), me
}
}
v := db.s.version()
- value, cSched, err := v.get(ikey, ro, false)
+ value, cSched, err := v.get(auxt, ikey, ro, false)
v.release()
if cSched {
// Trigger table compaction.
- db.compSendTrigger(db.tcompCmdC)
+ db.compTrigger(db.tcompCmdC)
}
return
}
-func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err error) {
- ikey := newIkey(key, seq, ktSeek)
+func nilIfNotFound(err error) error {
+ if err == ErrNotFound {
+ return nil
+ }
+ return err
+}
+
+func (db *DB) has(auxm *memdb.DB, auxt tFiles, key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err error) {
+ ikey := makeIkey(nil, key, seq, ktSeek)
+
+ if auxm != nil {
+ if ok, _, me := memGet(auxm, ikey, db.s.icmp); ok {
+ return me == nil, nilIfNotFound(me)
+ }
+ }
em, fm := db.getMems()
for _, m := range [...]*memDB{em, fm} {
@@ -652,30 +803,17 @@ func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err er
}
defer m.decref()
- mk, _, me := m.mdb.Find(ikey)
- if me == nil {
- ukey, _, kt, kerr := parseIkey(mk)
- if kerr != nil {
- // Shouldn't have had happen.
- panic(kerr)
- }
- if db.s.icmp.uCompare(ukey, key) == 0 {
- if kt == ktDel {
- return false, nil
- }
- return true, nil
- }
- } else if me != ErrNotFound {
- return false, me
+ if ok, _, me := memGet(m.DB, ikey, db.s.icmp); ok {
+ return me == nil, nilIfNotFound(me)
}
}
v := db.s.version()
- _, cSched, err := v.get(ikey, ro, true)
+ _, cSched, err := v.get(auxt, ikey, ro, true)
v.release()
if cSched {
// Trigger table compaction.
- db.compSendTrigger(db.tcompCmdC)
+ db.compTrigger(db.tcompCmdC)
}
if err == nil {
ret = true
@@ -699,7 +837,7 @@ func (db *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
se := db.acquireSnapshot()
defer db.releaseSnapshot(se)
- return db.get(key, se.seq, ro)
+ return db.get(nil, nil, key, se.seq, ro)
}
// Has returns true if the DB does contains the given key.
@@ -713,11 +851,11 @@ func (db *DB) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) {
se := db.acquireSnapshot()
defer db.releaseSnapshot(se)
- return db.has(key, se.seq, ro)
+ return db.has(nil, nil, key, se.seq, ro)
}
// NewIterator returns an iterator for the latest snapshot of the
-// uderlying DB.
+// underlying DB.
// The returned iterator is not goroutine-safe, but it is safe to use
// multiple iterators concurrently, with each in a dedicated goroutine.
// It is also safe to use an iterator concurrently with modifying its
@@ -741,7 +879,7 @@ func (db *DB) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Itera
defer db.releaseSnapshot(se)
// Iterator holds 'version' lock, 'version' is immutable so snapshot
// can be released after iterator created.
- return db.newIterator(se.seq, slice, ro)
+ return db.newIterator(nil, nil, se.seq, slice, ro)
}
// GetSnapshot returns a latest snapshot of the underlying DB. A snapshot
@@ -784,7 +922,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
const prefix = "leveldb."
if !strings.HasPrefix(name, prefix) {
- return "", errors.New("leveldb: GetProperty: unknown property: " + name)
+ return "", ErrNotFound
}
p := name[len(prefix):]
@@ -797,8 +935,8 @@ func (db *DB) GetProperty(name string) (value string, err error) {
var level uint
var rest string
n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest)
- if n != 1 || int(level) >= db.s.o.GetNumLevel() {
- err = errors.New("leveldb: GetProperty: invalid property: " + name)
+ if n != 1 {
+ err = ErrNotFound
} else {
value = fmt.Sprint(v.tLen(int(level)))
}
@@ -806,8 +944,8 @@ func (db *DB) GetProperty(name string) (value string, err error) {
value = "Compactions\n" +
" Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)\n" +
"-------+------------+---------------+---------------+---------------+---------------\n"
- for level, tables := range v.tables {
- duration, read, write := db.compStats[level].get()
+ for level, tables := range v.levels {
+ duration, read, write := db.compStats.getStat(level)
if len(tables) == 0 && duration == 0 {
continue
}
@@ -816,10 +954,10 @@ func (db *DB) GetProperty(name string) (value string, err error) {
float64(read)/1048576.0, float64(write)/1048576.0)
}
case p == "sstables":
- for level, tables := range v.tables {
+ for level, tables := range v.levels {
value += fmt.Sprintf("--- level %d ---\n", level)
for _, t := range tables {
- value += fmt.Sprintf("%d:%d[%q .. %q]\n", t.file.Num(), t.size, t.imin, t.imax)
+ value += fmt.Sprintf("%d:%d[%q .. %q]\n", t.fd.Num, t.size, t.imin, t.imax)
}
}
case p == "blockpool":
@@ -837,7 +975,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
case p == "aliveiters":
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters))
default:
- err = errors.New("leveldb: GetProperty: unknown property: " + name)
+ err = ErrNotFound
}
return
@@ -859,8 +997,8 @@ func (db *DB) SizeOf(ranges []util.Range) (Sizes, error) {
sizes := make(Sizes, 0, len(ranges))
for _, r := range ranges {
- imin := newIkey(r.Start, kMaxSeq, ktSeek)
- imax := newIkey(r.Limit, kMaxSeq, ktSeek)
+ imin := makeIkey(nil, r.Start, kMaxSeq, ktSeek)
+ imax := makeIkey(nil, r.Limit, kMaxSeq, ktSeek)
start, err := v.offsetOf(imin)
if err != nil {
return nil, err
@@ -879,8 +1017,8 @@ func (db *DB) SizeOf(ranges []util.Range) (Sizes, error) {
return sizes, nil
}
-// Close closes the DB. This will also releases any outstanding snapshot and
-// abort any in-flight compaction.
+// Close closes the DB. This will also releases any outstanding snapshot,
+// abort any in-flight compaction and discard open transaction.
//
// It is not safe to close a DB until all outstanding iterators are released.
// It is valid to call Close multiple times. Other methods should not be
@@ -900,17 +1038,27 @@ func (db *DB) Close() error {
var err error
select {
case err = <-db.compErrC:
+ if err == ErrReadOnly {
+ err = nil
+ }
default:
}
// Signal all goroutines.
close(db.closeC)
+ // Discard open transaction.
+ if db.tr != nil {
+ db.tr.Discard()
+ }
+
+ // Acquire writer lock.
+ db.writeLockC <- struct{}{}
+
// Wait for all gorotines to exit.
db.closeW.Wait()
- // Lock writer and closes journal.
- db.writeLockC <- struct{}{}
+ // Closes journal.
if db.journal != nil {
db.journal.Close()
db.journalWriter.Close()
@@ -937,8 +1085,6 @@ func (db *DB) Close() error {
db.frozenMem = nil
db.journal = nil
db.journalWriter = nil
- db.journalFile = nil
- db.frozenJournalFile = nil
db.closer = nil
return err
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go
index 447407aba..a94cf4c84 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_compaction.go
@@ -11,109 +11,79 @@ import (
"time"
"github.com/syndtr/goleveldb/leveldb/errors"
- "github.com/syndtr/goleveldb/leveldb/memdb"
"github.com/syndtr/goleveldb/leveldb/opt"
+ "github.com/syndtr/goleveldb/leveldb/storage"
)
var (
errCompactionTransactExiting = errors.New("leveldb: compaction transact exiting")
)
-type cStats struct {
- sync.Mutex
+type cStat struct {
duration time.Duration
- read uint64
- write uint64
+ read int64
+ write int64
}
-func (p *cStats) add(n *cStatsStaging) {
- p.Lock()
+func (p *cStat) add(n *cStatStaging) {
p.duration += n.duration
p.read += n.read
p.write += n.write
- p.Unlock()
}
-func (p *cStats) get() (duration time.Duration, read, write uint64) {
- p.Lock()
- defer p.Unlock()
+func (p *cStat) get() (duration time.Duration, read, write int64) {
return p.duration, p.read, p.write
}
-type cStatsStaging struct {
+type cStatStaging struct {
start time.Time
duration time.Duration
on bool
- read uint64
- write uint64
+ read int64
+ write int64
}
-func (p *cStatsStaging) startTimer() {
+func (p *cStatStaging) startTimer() {
if !p.on {
p.start = time.Now()
p.on = true
}
}
-func (p *cStatsStaging) stopTimer() {
+func (p *cStatStaging) stopTimer() {
if p.on {
p.duration += time.Since(p.start)
p.on = false
}
}
-type cMem struct {
- s *session
- level int
- rec *sessionRecord
-}
-
-func newCMem(s *session) *cMem {
- return &cMem{s: s, rec: &sessionRecord{numLevel: s.o.GetNumLevel()}}
+type cStats struct {
+ lk sync.Mutex
+ stats []cStat
}
-func (c *cMem) flush(mem *memdb.DB, level int) error {
- s := c.s
-
- // Write memdb to table.
- iter := mem.NewIterator(nil)
- defer iter.Release()
- t, n, err := s.tops.createFrom(iter)
- if err != nil {
- return err
- }
-
- // Pick level.
- if level < 0 {
- v := s.version()
- level = v.pickLevel(t.imin.ukey(), t.imax.ukey())
- v.release()
+func (p *cStats) addStat(level int, n *cStatStaging) {
+ p.lk.Lock()
+ if level >= len(p.stats) {
+ newStats := make([]cStat, level+1)
+ copy(newStats, p.stats)
+ p.stats = newStats
}
- c.rec.addTableFile(level, t)
-
- s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
-
- c.level = level
- return nil
+ p.stats[level].add(n)
+ p.lk.Unlock()
}
-func (c *cMem) reset() {
- c.rec = &sessionRecord{numLevel: c.s.o.GetNumLevel()}
-}
-
-func (c *cMem) commit(journal, seq uint64) error {
- c.rec.setJournalNum(journal)
- c.rec.setSeqNum(seq)
-
- // Commit changes.
- return c.s.commit(c.rec)
+func (p *cStats) getStat(level int) (duration time.Duration, read, write int64) {
+ p.lk.Lock()
+ defer p.lk.Unlock()
+ if level < len(p.stats) {
+ return p.stats[level].get()
+ }
+ return
}
func (db *DB) compactionError() {
- var (
- err error
- wlocked bool
- )
+ var err error
noerr:
// No error.
for {
@@ -121,7 +91,7 @@ noerr:
case err = <-db.compErrSetC:
switch {
case err == nil:
- case errors.IsCorrupted(err):
+ case err == ErrReadOnly, errors.IsCorrupted(err):
goto hasperr
default:
goto haserr
@@ -139,7 +109,7 @@ haserr:
switch {
case err == nil:
goto noerr
- case errors.IsCorrupted(err):
+ case err == ErrReadOnly, errors.IsCorrupted(err):
goto hasperr
default:
}
@@ -155,9 +125,9 @@ hasperr:
case db.compPerErrC <- err:
case db.writeLockC <- struct{}{}:
// Hold write lock, so that write won't pass-through.
- wlocked = true
+ db.compWriteLocking = true
case _, _ = <-db.closeC:
- if wlocked {
+ if db.compWriteLocking {
// We should release the lock or Close will hang.
<-db.writeLockC
}
@@ -286,22 +256,27 @@ func (db *DB) compactionExitTransact() {
panic(errCompactionTransactExiting)
}
+func (db *DB) compactionCommit(name string, rec *sessionRecord) {
+ db.compCommitLk.Lock()
+ defer db.compCommitLk.Unlock() // Defer is necessary.
+ db.compactionTransactFunc(name+"@commit", func(cnt *compactionTransactCounter) error {
+ return db.s.commit(rec)
+ }, nil)
+}
+
func (db *DB) memCompaction() {
- mem := db.getFrozenMem()
- if mem == nil {
+ mdb := db.getFrozenMem()
+ if mdb == nil {
return
}
- defer mem.decref()
-
- c := newCMem(db.s)
- stats := new(cStatsStaging)
+ defer mdb.decref()
- db.logf("mem@flush N·%d S·%s", mem.mdb.Len(), shortenb(mem.mdb.Size()))
+ db.logf("memdb@flush N·%d S·%s", mdb.Len(), shortenb(mdb.Size()))
// Don't compact empty memdb.
- if mem.mdb.Len() == 0 {
- db.logf("mem@flush skipping")
- // drop frozen mem
+ if mdb.Len() == 0 {
+ db.logf("memdb@flush skipping")
+ // drop frozen memdb
db.dropFrozenMem()
return
}
@@ -317,35 +292,44 @@ func (db *DB) memCompaction() {
return
}
- db.compactionTransactFunc("mem@flush", func(cnt *compactionTransactCounter) (err error) {
+ var (
+ rec = &sessionRecord{}
+ stats = &cStatStaging{}
+ flushLevel int
+ )
+
+ // Generate tables.
+ db.compactionTransactFunc("memdb@flush", func(cnt *compactionTransactCounter) (err error) {
stats.startTimer()
- defer stats.stopTimer()
- return c.flush(mem.mdb, -1)
+ flushLevel, err = db.s.flushMemdb(rec, mdb.DB, db.memdbMaxLevel)
+ stats.stopTimer()
+ return
}, func() error {
- for _, r := range c.rec.addedTables {
- db.logf("mem@flush revert @%d", r.num)
- f := db.s.getTableFile(r.num)
- if err := f.Remove(); err != nil {
+ for _, r := range rec.addedTables {
+ db.logf("memdb@flush revert @%d", r.num)
+ if err := db.s.stor.Remove(storage.FileDesc{Type: storage.TypeTable, Num: r.num}); err != nil {
return err
}
}
return nil
})
- db.compactionTransactFunc("mem@commit", func(cnt *compactionTransactCounter) (err error) {
- stats.startTimer()
- defer stats.stopTimer()
- return c.commit(db.journalFile.Num(), db.frozenSeq)
- }, nil)
+ rec.setJournalNum(db.journalFd.Num)
+ rec.setSeqNum(db.frozenSeq)
+
+ // Commit.
+ stats.startTimer()
+ db.compactionCommit("memdb", rec)
+ stats.stopTimer()
- db.logf("mem@flush committed F·%d T·%v", len(c.rec.addedTables), stats.duration)
+ db.logf("memdb@flush committed F·%d T·%v", len(rec.addedTables), stats.duration)
- for _, r := range c.rec.addedTables {
+ for _, r := range rec.addedTables {
stats.write += r.size
}
- db.compStats[c.level].add(stats)
+ db.compStats.addStat(flushLevel, stats)
- // Drop frozen mem.
+ // Drop frozen memdb.
db.dropFrozenMem()
// Resume table compaction.
@@ -359,7 +343,7 @@ func (db *DB) memCompaction() {
}
// Trigger table compaction.
- db.compSendTrigger(db.tcompCmdC)
+ db.compTrigger(db.tcompCmdC)
}
type tableCompactionBuilder struct {
@@ -367,7 +351,7 @@ type tableCompactionBuilder struct {
s *session
c *compaction
rec *sessionRecord
- stat0, stat1 *cStatsStaging
+ stat0, stat1 *cStatStaging
snapHasLastUkey bool
snapLastUkey []byte
@@ -421,9 +405,9 @@ func (b *tableCompactionBuilder) flush() error {
if err != nil {
return err
}
- b.rec.addTableFile(b.c.level+1, t)
+ b.rec.addTableFile(b.c.sourceLevel+1, t)
b.stat1.write += t.size
- b.s.logf("table@build created L%d@%d N·%d S·%s %q:%q", b.c.level+1, t.file.Num(), b.tw.tw.EntriesLen(), shortenb(int(t.size)), t.imin, t.imax)
+ b.s.logf("table@build created L%d@%d N·%d S·%s %q:%q", b.c.sourceLevel+1, t.fd.Num, b.tw.tw.EntriesLen(), shortenb(int(t.size)), t.imin, t.imax)
b.tw = nil
return nil
}
@@ -546,8 +530,7 @@ func (b *tableCompactionBuilder) run(cnt *compactionTransactCounter) error {
func (b *tableCompactionBuilder) revert() error {
for _, at := range b.rec.addedTables {
b.s.logf("table@build revert @%d", at.num)
- f := b.s.getTableFile(at.num)
- if err := f.Remove(); err != nil {
+ if err := b.s.stor.Remove(storage.FileDesc{Type: storage.TypeTable, Num: at.num}); err != nil {
return err
}
}
@@ -557,31 +540,31 @@ func (b *tableCompactionBuilder) revert() error {
func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
defer c.release()
- rec := &sessionRecord{numLevel: db.s.o.GetNumLevel()}
- rec.addCompPtr(c.level, c.imax)
+ rec := &sessionRecord{}
+ rec.addCompPtr(c.sourceLevel, c.imax)
if !noTrivial && c.trivial() {
- t := c.tables[0][0]
- db.logf("table@move L%d@%d -> L%d", c.level, t.file.Num(), c.level+1)
- rec.delTable(c.level, t.file.Num())
- rec.addTableFile(c.level+1, t)
+ t := c.levels[0][0]
+ db.logf("table@move L%d@%d -> L%d", c.sourceLevel, t.fd.Num, c.sourceLevel+1)
+ rec.delTable(c.sourceLevel, t.fd.Num)
+ rec.addTableFile(c.sourceLevel+1, t)
db.compactionTransactFunc("table@move", func(cnt *compactionTransactCounter) (err error) {
return db.s.commit(rec)
}, nil)
return
}
- var stats [2]cStatsStaging
- for i, tables := range c.tables {
+ var stats [2]cStatStaging
+ for i, tables := range c.levels {
for _, t := range tables {
stats[i].read += t.size
// Insert deleted tables into record
- rec.delTable(c.level+i, t.file.Num())
+ rec.delTable(c.sourceLevel+i, t.fd.Num)
}
}
sourceSize := int(stats[0].read + stats[1].read)
minSeq := db.minSeq()
- db.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.level, len(c.tables[0]), c.level+1, len(c.tables[1]), shortenb(sourceSize), minSeq)
+ db.logf("table@compaction L%d·%d -> L%d·%d S·%s Q·%d", c.sourceLevel, len(c.levels[0]), c.sourceLevel+1, len(c.levels[1]), shortenb(sourceSize), minSeq)
b := &tableCompactionBuilder{
db: db,
@@ -591,49 +574,60 @@ func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
stat1: &stats[1],
minSeq: minSeq,
strict: db.s.o.GetStrict(opt.StrictCompaction),
- tableSize: db.s.o.GetCompactionTableSize(c.level + 1),
+ tableSize: db.s.o.GetCompactionTableSize(c.sourceLevel + 1),
}
db.compactionTransact("table@build", b)
- // Commit changes
- db.compactionTransactFunc("table@commit", func(cnt *compactionTransactCounter) (err error) {
- stats[1].startTimer()
- defer stats[1].stopTimer()
- return db.s.commit(rec)
- }, nil)
+ // Commit.
+ stats[1].startTimer()
+ db.compactionCommit("table", rec)
+ stats[1].stopTimer()
resultSize := int(stats[1].write)
db.logf("table@compaction committed F%s S%s Ke·%d D·%d T·%v", sint(len(rec.addedTables)-len(rec.deletedTables)), sshortenb(resultSize-sourceSize), b.kerrCnt, b.dropCnt, stats[1].duration)
// Save compaction stats
for i := range stats {
- db.compStats[c.level+1].add(&stats[i])
+ db.compStats.addStat(c.sourceLevel+1, &stats[i])
}
}
-func (db *DB) tableRangeCompaction(level int, umin, umax []byte) {
+func (db *DB) tableRangeCompaction(level int, umin, umax []byte) error {
db.logf("table@compaction range L%d %q:%q", level, umin, umax)
-
if level >= 0 {
- if c := db.s.getCompactionRange(level, umin, umax); c != nil {
+ if c := db.s.getCompactionRange(level, umin, umax, true); c != nil {
db.tableCompaction(c, true)
}
} else {
- v := db.s.version()
- m := 1
- for i, t := range v.tables[1:] {
- if t.overlaps(db.s.icmp, umin, umax, false) {
- m = i + 1
+ // Retry until nothing to compact.
+ for {
+ compacted := false
+
+ // Scan for maximum level with overlapped tables.
+ v := db.s.version()
+ m := 1
+ for i := m; i < len(v.levels); i++ {
+ tables := v.levels[i]
+ if tables.overlaps(db.s.icmp, umin, umax, false) {
+ m = i
+ }
}
- }
- v.release()
+ v.release()
- for level := 0; level < m; level++ {
- if c := db.s.getCompactionRange(level, umin, umax); c != nil {
- db.tableCompaction(c, true)
+ for level := 0; level < m; level++ {
+ if c := db.s.getCompactionRange(level, umin, umax, false); c != nil {
+ db.tableCompaction(c, true)
+ compacted = true
+ }
+ }
+
+ if !compacted {
+ break
}
}
}
+
+ return nil
}
func (db *DB) tableAutoCompaction() {
@@ -660,11 +654,11 @@ type cCmd interface {
ack(err error)
}
-type cIdle struct {
+type cAuto struct {
ackC chan<- error
}
-func (r cIdle) ack(err error) {
+func (r cAuto) ack(err error) {
if r.ackC != nil {
defer func() {
recover()
@@ -688,13 +682,21 @@ func (r cRange) ack(err error) {
}
}
+// This will trigger auto compaction but will not wait for it.
+func (db *DB) compTrigger(compC chan<- cCmd) {
+ select {
+ case compC <- cAuto{}:
+ default:
+ }
+}
+
// This will trigger auto compation and/or wait for all compaction to be done.
-func (db *DB) compSendIdle(compC chan<- cCmd) (err error) {
+func (db *DB) compTriggerWait(compC chan<- cCmd) (err error) {
ch := make(chan error)
defer close(ch)
// Send cmd.
select {
- case compC <- cIdle{ch}:
+ case compC <- cAuto{ch}:
case err = <-db.compErrC:
return
case _, _ = <-db.closeC:
@@ -710,16 +712,8 @@ func (db *DB) compSendIdle(compC chan<- cCmd) (err error) {
return err
}
-// This will trigger auto compaction but will not wait for it.
-func (db *DB) compSendTrigger(compC chan<- cCmd) {
- select {
- case compC <- cIdle{}:
- default:
- }
-}
-
// Send range compaction request.
-func (db *DB) compSendRange(compC chan<- cCmd, level int, min, max []byte) (err error) {
+func (db *DB) compTriggerRange(compC chan<- cCmd, level int, min, max []byte) (err error) {
ch := make(chan error)
defer close(ch)
// Send cmd.
@@ -759,7 +753,7 @@ func (db *DB) mCompaction() {
select {
case x = <-db.mcompCmdC:
switch x.(type) {
- case cIdle:
+ case cAuto:
db.memCompaction()
x.ack(nil)
x = nil
@@ -820,11 +814,10 @@ func (db *DB) tCompaction() {
}
if x != nil {
switch cmd := x.(type) {
- case cIdle:
+ case cAuto:
ackQ = append(ackQ, x)
case cRange:
- db.tableRangeCompaction(cmd.level, cmd.min, cmd.max)
- x.ack(nil)
+ x.ack(db.tableRangeCompaction(cmd.level, cmd.min, cmd.max))
default:
panic("leveldb: unknown command")
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_iter.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_iter.go
index 011a94a35..86bcb99d9 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_iter.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_iter.go
@@ -33,40 +33,50 @@ func (mr *memdbReleaser) Release() {
})
}
-func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
+func (db *DB) newRawIterator(auxm *memDB, auxt tFiles, slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
+ strict := opt.GetStrict(db.s.o.Options, ro, opt.StrictReader)
em, fm := db.getMems()
v := db.s.version()
- ti := v.getIterators(slice, ro)
- n := len(ti) + 2
- i := make([]iterator.Iterator, 0, n)
- emi := em.mdb.NewIterator(slice)
+ tableIts := v.getIterators(slice, ro)
+ n := len(tableIts) + len(auxt) + 3
+ its := make([]iterator.Iterator, 0, n)
+
+ if auxm != nil {
+ ami := auxm.NewIterator(slice)
+ ami.SetReleaser(&memdbReleaser{m: auxm})
+ its = append(its, ami)
+ }
+ for _, t := range auxt {
+ its = append(its, v.s.tops.newIterator(t, slice, ro))
+ }
+
+ emi := em.NewIterator(slice)
emi.SetReleaser(&memdbReleaser{m: em})
- i = append(i, emi)
+ its = append(its, emi)
if fm != nil {
- fmi := fm.mdb.NewIterator(slice)
+ fmi := fm.NewIterator(slice)
fmi.SetReleaser(&memdbReleaser{m: fm})
- i = append(i, fmi)
+ its = append(its, fmi)
}
- i = append(i, ti...)
- strict := opt.GetStrict(db.s.o.Options, ro, opt.StrictReader)
- mi := iterator.NewMergedIterator(i, db.s.icmp, strict)
+ its = append(its, tableIts...)
+ mi := iterator.NewMergedIterator(its, db.s.icmp, strict)
mi.SetReleaser(&versionReleaser{v: v})
return mi
}
-func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *dbIter {
+func (db *DB) newIterator(auxm *memDB, auxt tFiles, seq uint64, slice *util.Range, ro *opt.ReadOptions) *dbIter {
var islice *util.Range
if slice != nil {
islice = &util.Range{}
if slice.Start != nil {
- islice.Start = newIkey(slice.Start, kMaxSeq, ktSeek)
+ islice.Start = makeIkey(nil, slice.Start, kMaxSeq, ktSeek)
}
if slice.Limit != nil {
- islice.Limit = newIkey(slice.Limit, kMaxSeq, ktSeek)
+ islice.Limit = makeIkey(nil, slice.Limit, kMaxSeq, ktSeek)
}
}
- rawIter := db.newRawIterator(islice, ro)
+ rawIter := db.newRawIterator(auxm, auxt, islice, ro)
iter := &dbIter{
db: db,
icmp: db.s.icmp,
@@ -177,7 +187,7 @@ func (i *dbIter) Seek(key []byte) bool {
return false
}
- ikey := newIkey(key, i.seq, ktSeek)
+ ikey := makeIkey(nil, key, i.seq, ktSeek)
if i.iter.Seek(ikey) {
i.dir = dirSOI
return i.next()
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go
index 0372848ff..977f65ba5 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_snapshot.go
@@ -110,7 +110,7 @@ func (snap *Snapshot) Get(key []byte, ro *opt.ReadOptions) (value []byte, err er
err = ErrSnapshotReleased
return
}
- return snap.db.get(key, snap.elem.seq, ro)
+ return snap.db.get(nil, nil, key, snap.elem.seq, ro)
}
// Has returns true if the DB does contains the given key.
@@ -127,10 +127,10 @@ func (snap *Snapshot) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error)
err = ErrSnapshotReleased
return
}
- return snap.db.has(key, snap.elem.seq, ro)
+ return snap.db.has(nil, nil, key, snap.elem.seq, ro)
}
-// NewIterator returns an iterator for the snapshot of the uderlying DB.
+// NewIterator returns an iterator for the snapshot of the underlying DB.
// The returned iterator is not goroutine-safe, but it is safe to use
// multiple iterators concurrently, with each in a dedicated goroutine.
// It is also safe to use an iterator concurrently with modifying its
@@ -158,7 +158,7 @@ func (snap *Snapshot) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterat
}
// Since iterator already hold version ref, it doesn't need to
// hold snapshot ref.
- return snap.db.newIterator(snap.elem.seq, slice, ro)
+ return snap.db.newIterator(nil, nil, snap.elem.seq, slice, ro)
}
// Release releases the snapshot. This will not release any returned
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_state.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_state.go
index d4db9d6dd..0207e221e 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_state.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_state.go
@@ -12,14 +12,19 @@ import (
"github.com/syndtr/goleveldb/leveldb/journal"
"github.com/syndtr/goleveldb/leveldb/memdb"
+ "github.com/syndtr/goleveldb/leveldb/storage"
)
type memDB struct {
- db *DB
- mdb *memdb.DB
+ db *DB
+ *memdb.DB
ref int32
}
+func (m *memDB) getref() int32 {
+ return atomic.LoadInt32(&m.ref)
+}
+
func (m *memDB) incref() {
atomic.AddInt32(&m.ref, 1)
}
@@ -27,12 +32,12 @@ func (m *memDB) incref() {
func (m *memDB) decref() {
if ref := atomic.AddInt32(&m.ref, -1); ref == 0 {
// Only put back memdb with std capacity.
- if m.mdb.Capacity() == m.db.s.o.GetWriteBuffer() {
- m.mdb.Reset()
- m.db.mpoolPut(m.mdb)
+ if m.Capacity() == m.db.s.o.GetWriteBuffer() {
+ m.Reset()
+ m.db.mpoolPut(m.DB)
}
m.db = nil
- m.mdb = nil
+ m.DB = nil
} else if ref < 0 {
panic("negative memdb ref")
}
@@ -48,11 +53,15 @@ func (db *DB) addSeq(delta uint64) {
atomic.AddUint64(&db.seq, delta)
}
+func (db *DB) setSeq(seq uint64) {
+ atomic.StoreUint64(&db.seq, seq)
+}
+
func (db *DB) sampleSeek(ikey iKey) {
v := db.s.version()
if v.sampleSeek(ikey) {
// Trigger table compaction.
- db.compSendTrigger(db.tcompCmdC)
+ db.compTrigger(db.tcompCmdC)
}
v.release()
}
@@ -67,12 +76,18 @@ func (db *DB) mpoolPut(mem *memdb.DB) {
}
}
-func (db *DB) mpoolGet() *memdb.DB {
+func (db *DB) mpoolGet(n int) *memDB {
+ var mdb *memdb.DB
select {
- case mem := <-db.memPool:
- return mem
+ case mdb = <-db.memPool:
default:
- return nil
+ }
+ if mdb == nil || mdb.Capacity() < n {
+ mdb = memdb.New(db.s.icmp, maxInt(db.s.o.GetWriteBuffer(), n))
+ }
+ return &memDB{
+ db: db,
+ DB: mdb,
}
}
@@ -95,11 +110,10 @@ func (db *DB) mpoolDrain() {
// Create new memdb and froze the old one; need external synchronization.
// newMem only called synchronously by the writer.
func (db *DB) newMem(n int) (mem *memDB, err error) {
- num := db.s.allocFileNum()
- file := db.s.getJournalFile(num)
- w, err := file.Create()
+ fd := storage.FileDesc{Type: storage.TypeJournal, Num: db.s.allocFileNum()}
+ w, err := db.s.stor.Create(fd)
if err != nil {
- db.s.reuseFileNum(num)
+ db.s.reuseFileNum(fd.Num)
return
}
@@ -115,20 +129,14 @@ func (db *DB) newMem(n int) (mem *memDB, err error) {
} else {
db.journal.Reset(w)
db.journalWriter.Close()
- db.frozenJournalFile = db.journalFile
+ db.frozenJournalFd = db.journalFd
}
db.journalWriter = w
- db.journalFile = file
+ db.journalFd = fd
db.frozenMem = db.mem
- mdb := db.mpoolGet()
- if mdb == nil || mdb.Capacity() < n {
- mdb = memdb.New(db.s.icmp, maxInt(db.s.o.GetWriteBuffer(), n))
- }
- mem = &memDB{
- db: db,
- mdb: mdb,
- ref: 2,
- }
+ mem = db.mpoolGet(n)
+ mem.incref() // for self
+ mem.incref() // for caller
db.mem = mem
// The seq only incremented by the writer. And whoever called newMem
// should hold write lock, so no need additional synchronization here.
@@ -181,12 +189,12 @@ func (db *DB) getFrozenMem() *memDB {
// Drop frozen memdb; assume that frozen memdb isn't nil.
func (db *DB) dropFrozenMem() {
db.memMu.Lock()
- if err := db.frozenJournalFile.Remove(); err != nil {
- db.logf("journal@remove removing @%d %q", db.frozenJournalFile.Num(), err)
+ if err := db.s.stor.Remove(db.frozenJournalFd); err != nil {
+ db.logf("journal@remove removing @%d %q", db.frozenJournalFd.Num, err)
} else {
- db.logf("journal@remove removed @%d", db.frozenJournalFile.Num())
+ db.logf("journal@remove removed @%d", db.frozenJournalFd.Num)
}
- db.frozenJournalFile = nil
+ db.frozenJournalFd = storage.FileDesc{}
db.frozenMem.decref()
db.frozenMem = nil
db.memMu.Unlock()
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_test.go
deleted file mode 100644
index 38bfbf1ea..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_test.go
+++ /dev/null
@@ -1,2665 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- "bytes"
- "container/list"
- crand "crypto/rand"
- "encoding/binary"
- "fmt"
- "math/rand"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
- "testing"
- "time"
- "unsafe"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
- "github.com/syndtr/goleveldb/leveldb/errors"
- "github.com/syndtr/goleveldb/leveldb/filter"
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/storage"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-func tkey(i int) []byte {
- return []byte(fmt.Sprintf("%016d", i))
-}
-
-func tval(seed, n int) []byte {
- r := rand.New(rand.NewSource(int64(seed)))
- return randomString(r, n)
-}
-
-type dbHarness struct {
- t *testing.T
-
- stor *testStorage
- db *DB
- o *opt.Options
- ro *opt.ReadOptions
- wo *opt.WriteOptions
-}
-
-func newDbHarnessWopt(t *testing.T, o *opt.Options) *dbHarness {
- h := new(dbHarness)
- h.init(t, o)
- return h
-}
-
-func newDbHarness(t *testing.T) *dbHarness {
- return newDbHarnessWopt(t, &opt.Options{})
-}
-
-func (h *dbHarness) init(t *testing.T, o *opt.Options) {
- h.t = t
- h.stor = newTestStorage(t)
- h.o = o
- h.ro = nil
- h.wo = nil
-
- if err := h.openDB0(); err != nil {
- // So that it will come after fatal message.
- defer h.stor.Close()
- h.t.Fatal("Open (init): got error: ", err)
- }
-}
-
-func (h *dbHarness) openDB0() (err error) {
- h.t.Log("opening DB")
- h.db, err = Open(h.stor, h.o)
- return
-}
-
-func (h *dbHarness) openDB() {
- if err := h.openDB0(); err != nil {
- h.t.Fatal("Open: got error: ", err)
- }
-}
-
-func (h *dbHarness) closeDB0() error {
- h.t.Log("closing DB")
- return h.db.Close()
-}
-
-func (h *dbHarness) closeDB() {
- if err := h.closeDB0(); err != nil {
- h.t.Error("Close: got error: ", err)
- }
- h.stor.CloseCheck()
- runtime.GC()
-}
-
-func (h *dbHarness) reopenDB() {
- h.closeDB()
- h.openDB()
-}
-
-func (h *dbHarness) close() {
- h.closeDB0()
- h.db = nil
- h.stor.Close()
- h.stor = nil
- runtime.GC()
-}
-
-func (h *dbHarness) openAssert(want bool) {
- db, err := Open(h.stor, h.o)
- if err != nil {
- if want {
- h.t.Error("Open: assert: got error: ", err)
- } else {
- h.t.Log("Open: assert: got error (expected): ", err)
- }
- } else {
- if !want {
- h.t.Error("Open: assert: expect error")
- }
- db.Close()
- }
-}
-
-func (h *dbHarness) write(batch *Batch) {
- if err := h.db.Write(batch, h.wo); err != nil {
- h.t.Error("Write: got error: ", err)
- }
-}
-
-func (h *dbHarness) put(key, value string) {
- if err := h.db.Put([]byte(key), []byte(value), h.wo); err != nil {
- h.t.Error("Put: got error: ", err)
- }
-}
-
-func (h *dbHarness) putMulti(n int, low, hi string) {
- for i := 0; i < n; i++ {
- h.put(low, "begin")
- h.put(hi, "end")
- h.compactMem()
- }
-}
-
-func (h *dbHarness) maxNextLevelOverlappingBytes(want uint64) {
- t := h.t
- db := h.db
-
- var (
- maxOverlaps uint64
- maxLevel int
- )
- v := db.s.version()
- for i, tt := range v.tables[1 : len(v.tables)-1] {
- level := i + 1
- next := v.tables[level+1]
- for _, t := range tt {
- r := next.getOverlaps(nil, db.s.icmp, t.imin.ukey(), t.imax.ukey(), false)
- sum := r.size()
- if sum > maxOverlaps {
- maxOverlaps = sum
- maxLevel = level
- }
- }
- }
- v.release()
-
- if maxOverlaps > want {
- t.Errorf("next level most overlapping bytes is more than %d, got=%d level=%d", want, maxOverlaps, maxLevel)
- } else {
- t.Logf("next level most overlapping bytes is %d, level=%d want=%d", maxOverlaps, maxLevel, want)
- }
-}
-
-func (h *dbHarness) delete(key string) {
- t := h.t
- db := h.db
-
- err := db.Delete([]byte(key), h.wo)
- if err != nil {
- t.Error("Delete: got error: ", err)
- }
-}
-
-func (h *dbHarness) assertNumKeys(want int) {
- iter := h.db.NewIterator(nil, h.ro)
- defer iter.Release()
- got := 0
- for iter.Next() {
- got++
- }
- if err := iter.Error(); err != nil {
- h.t.Error("assertNumKeys: ", err)
- }
- if want != got {
- h.t.Errorf("assertNumKeys: want=%d got=%d", want, got)
- }
-}
-
-func (h *dbHarness) getr(db Reader, key string, expectFound bool) (found bool, v []byte) {
- t := h.t
- v, err := db.Get([]byte(key), h.ro)
- switch err {
- case ErrNotFound:
- if expectFound {
- t.Errorf("Get: key '%s' not found, want found", key)
- }
- case nil:
- found = true
- if !expectFound {
- t.Errorf("Get: key '%s' found, want not found", key)
- }
- default:
- t.Error("Get: got error: ", err)
- }
- return
-}
-
-func (h *dbHarness) get(key string, expectFound bool) (found bool, v []byte) {
- return h.getr(h.db, key, expectFound)
-}
-
-func (h *dbHarness) getValr(db Reader, key, value string) {
- t := h.t
- found, r := h.getr(db, key, true)
- if !found {
- return
- }
- rval := string(r)
- if rval != value {
- t.Errorf("Get: invalid value, got '%s', want '%s'", rval, value)
- }
-}
-
-func (h *dbHarness) getVal(key, value string) {
- h.getValr(h.db, key, value)
-}
-
-func (h *dbHarness) allEntriesFor(key, want string) {
- t := h.t
- db := h.db
- s := db.s
-
- ikey := newIkey([]byte(key), kMaxSeq, ktVal)
- iter := db.newRawIterator(nil, nil)
- if !iter.Seek(ikey) && iter.Error() != nil {
- t.Error("AllEntries: error during seek, err: ", iter.Error())
- return
- }
- res := "[ "
- first := true
- for iter.Valid() {
- if ukey, _, kt, kerr := parseIkey(iter.Key()); kerr == nil {
- if s.icmp.uCompare(ikey.ukey(), ukey) != 0 {
- break
- }
- if !first {
- res += ", "
- }
- first = false
- switch kt {
- case ktVal:
- res += string(iter.Value())
- case ktDel:
- res += "DEL"
- }
- } else {
- if !first {
- res += ", "
- }
- first = false
- res += "CORRUPTED"
- }
- iter.Next()
- }
- if !first {
- res += " "
- }
- res += "]"
- if res != want {
- t.Errorf("AllEntries: assert failed for key %q, got=%q want=%q", key, res, want)
- }
-}
-
-// Return a string that contains all key,value pairs in order,
-// formatted like "(k1->v1)(k2->v2)".
-func (h *dbHarness) getKeyVal(want string) {
- t := h.t
- db := h.db
-
- s, err := db.GetSnapshot()
- if err != nil {
- t.Fatal("GetSnapshot: got error: ", err)
- }
- res := ""
- iter := s.NewIterator(nil, nil)
- for iter.Next() {
- res += fmt.Sprintf("(%s->%s)", string(iter.Key()), string(iter.Value()))
- }
- iter.Release()
-
- if res != want {
- t.Errorf("GetKeyVal: invalid key/value pair, got=%q want=%q", res, want)
- }
- s.Release()
-}
-
-func (h *dbHarness) waitCompaction() {
- t := h.t
- db := h.db
- if err := db.compSendIdle(db.tcompCmdC); err != nil {
- t.Error("compaction error: ", err)
- }
-}
-
-func (h *dbHarness) waitMemCompaction() {
- t := h.t
- db := h.db
-
- if err := db.compSendIdle(db.mcompCmdC); err != nil {
- t.Error("compaction error: ", err)
- }
-}
-
-func (h *dbHarness) compactMem() {
- t := h.t
- db := h.db
-
- t.Log("starting memdb compaction")
-
- db.writeLockC <- struct{}{}
- defer func() {
- <-db.writeLockC
- }()
-
- if _, err := db.rotateMem(0); err != nil {
- t.Error("compaction error: ", err)
- }
- if err := db.compSendIdle(db.mcompCmdC); err != nil {
- t.Error("compaction error: ", err)
- }
-
- if h.totalTables() == 0 {
- t.Error("zero tables after mem compaction")
- }
-
- t.Log("memdb compaction done")
-}
-
-func (h *dbHarness) compactRangeAtErr(level int, min, max string, wanterr bool) {
- t := h.t
- db := h.db
-
- var _min, _max []byte
- if min != "" {
- _min = []byte(min)
- }
- if max != "" {
- _max = []byte(max)
- }
-
- t.Logf("starting table range compaction: level=%d, min=%q, max=%q", level, min, max)
-
- if err := db.compSendRange(db.tcompCmdC, level, _min, _max); err != nil {
- if wanterr {
- t.Log("CompactRangeAt: got error (expected): ", err)
- } else {
- t.Error("CompactRangeAt: got error: ", err)
- }
- } else if wanterr {
- t.Error("CompactRangeAt: expect error")
- }
-
- t.Log("table range compaction done")
-}
-
-func (h *dbHarness) compactRangeAt(level int, min, max string) {
- h.compactRangeAtErr(level, min, max, false)
-}
-
-func (h *dbHarness) compactRange(min, max string) {
- t := h.t
- db := h.db
-
- t.Logf("starting DB range compaction: min=%q, max=%q", min, max)
-
- var r util.Range
- if min != "" {
- r.Start = []byte(min)
- }
- if max != "" {
- r.Limit = []byte(max)
- }
- if err := db.CompactRange(r); err != nil {
- t.Error("CompactRange: got error: ", err)
- }
-
- t.Log("DB range compaction done")
-}
-
-func (h *dbHarness) sizeOf(start, limit string) uint64 {
- sz, err := h.db.SizeOf([]util.Range{
- {[]byte(start), []byte(limit)},
- })
- if err != nil {
- h.t.Error("SizeOf: got error: ", err)
- }
- return sz.Sum()
-}
-
-func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
- sz := h.sizeOf(start, limit)
- if sz < low || sz > hi {
- h.t.Errorf("sizeOf %q to %q not in range, want %d - %d, got %d",
- shorten(start), shorten(limit), low, hi, sz)
- }
-}
-
-func (h *dbHarness) getSnapshot() (s *Snapshot) {
- s, err := h.db.GetSnapshot()
- if err != nil {
- h.t.Fatal("GetSnapshot: got error: ", err)
- }
- return
-}
-func (h *dbHarness) tablesPerLevel(want string) {
- res := ""
- nz := 0
- v := h.db.s.version()
- for level, tt := range v.tables {
- if level > 0 {
- res += ","
- }
- res += fmt.Sprint(len(tt))
- if len(tt) > 0 {
- nz = len(res)
- }
- }
- v.release()
- res = res[:nz]
- if res != want {
- h.t.Errorf("invalid tables len, want=%s, got=%s", want, res)
- }
-}
-
-func (h *dbHarness) totalTables() (n int) {
- v := h.db.s.version()
- for _, tt := range v.tables {
- n += len(tt)
- }
- v.release()
- return
-}
-
-type keyValue interface {
- Key() []byte
- Value() []byte
-}
-
-func testKeyVal(t *testing.T, kv keyValue, want string) {
- res := string(kv.Key()) + "->" + string(kv.Value())
- if res != want {
- t.Errorf("invalid key/value, want=%q, got=%q", want, res)
- }
-}
-
-func numKey(num int) string {
- return fmt.Sprintf("key%06d", num)
-}
-
-var _bloom_filter = filter.NewBloomFilter(10)
-
-func truno(t *testing.T, o *opt.Options, f func(h *dbHarness)) {
- for i := 0; i < 4; i++ {
- func() {
- switch i {
- case 0:
- case 1:
- if o == nil {
- o = &opt.Options{Filter: _bloom_filter}
- } else {
- old := o
- o = &opt.Options{}
- *o = *old
- o.Filter = _bloom_filter
- }
- case 2:
- if o == nil {
- o = &opt.Options{Compression: opt.NoCompression}
- } else {
- old := o
- o = &opt.Options{}
- *o = *old
- o.Compression = opt.NoCompression
- }
- }
- h := newDbHarnessWopt(t, o)
- defer h.close()
- switch i {
- case 3:
- h.reopenDB()
- }
- f(h)
- }()
- }
-}
-
-func trun(t *testing.T, f func(h *dbHarness)) {
- truno(t, nil, f)
-}
-
-func testAligned(t *testing.T, name string, offset uintptr) {
- if offset%8 != 0 {
- t.Errorf("field %s offset is not 64-bit aligned", name)
- }
-}
-
-func Test_FieldsAligned(t *testing.T) {
- p1 := new(DB)
- testAligned(t, "DB.seq", unsafe.Offsetof(p1.seq))
- p2 := new(session)
- testAligned(t, "session.stNextFileNum", unsafe.Offsetof(p2.stNextFileNum))
- testAligned(t, "session.stJournalNum", unsafe.Offsetof(p2.stJournalNum))
- testAligned(t, "session.stPrevJournalNum", unsafe.Offsetof(p2.stPrevJournalNum))
- testAligned(t, "session.stSeqNum", unsafe.Offsetof(p2.stSeqNum))
-}
-
-func TestDB_Locking(t *testing.T) {
- h := newDbHarness(t)
- defer h.stor.Close()
- h.openAssert(false)
- h.closeDB()
- h.openAssert(true)
-}
-
-func TestDB_Empty(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.get("foo", false)
-
- h.reopenDB()
- h.get("foo", false)
- })
-}
-
-func TestDB_ReadWrite(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- h.getVal("foo", "v1")
- h.put("bar", "v2")
- h.put("foo", "v3")
- h.getVal("foo", "v3")
- h.getVal("bar", "v2")
-
- h.reopenDB()
- h.getVal("foo", "v3")
- h.getVal("bar", "v2")
- })
-}
-
-func TestDB_PutDeleteGet(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- h.getVal("foo", "v1")
- h.put("foo", "v2")
- h.getVal("foo", "v2")
- h.delete("foo")
- h.get("foo", false)
-
- h.reopenDB()
- h.get("foo", false)
- })
-}
-
-func TestDB_EmptyBatch(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.get("foo", false)
- err := h.db.Write(new(Batch), h.wo)
- if err != nil {
- t.Error("writing empty batch yield error: ", err)
- }
- h.get("foo", false)
-}
-
-func TestDB_GetFromFrozen(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 100100})
- defer h.close()
-
- h.put("foo", "v1")
- h.getVal("foo", "v1")
-
- h.stor.DelaySync(storage.TypeTable) // Block sync calls
- h.put("k1", strings.Repeat("x", 100000)) // Fill memtable
- h.put("k2", strings.Repeat("y", 100000)) // Trigger compaction
- for i := 0; h.db.getFrozenMem() == nil && i < 100; i++ {
- time.Sleep(10 * time.Microsecond)
- }
- if h.db.getFrozenMem() == nil {
- h.stor.ReleaseSync(storage.TypeTable)
- t.Fatal("No frozen mem")
- }
- h.getVal("foo", "v1")
- h.stor.ReleaseSync(storage.TypeTable) // Release sync calls
-
- h.reopenDB()
- h.getVal("foo", "v1")
- h.get("k1", true)
- h.get("k2", true)
-}
-
-func TestDB_GetFromTable(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- h.compactMem()
- h.getVal("foo", "v1")
- })
-}
-
-func TestDB_GetSnapshot(t *testing.T) {
- trun(t, func(h *dbHarness) {
- bar := strings.Repeat("b", 200)
- h.put("foo", "v1")
- h.put(bar, "v1")
-
- snap, err := h.db.GetSnapshot()
- if err != nil {
- t.Fatal("GetSnapshot: got error: ", err)
- }
-
- h.put("foo", "v2")
- h.put(bar, "v2")
-
- h.getVal("foo", "v2")
- h.getVal(bar, "v2")
- h.getValr(snap, "foo", "v1")
- h.getValr(snap, bar, "v1")
-
- h.compactMem()
-
- h.getVal("foo", "v2")
- h.getVal(bar, "v2")
- h.getValr(snap, "foo", "v1")
- h.getValr(snap, bar, "v1")
-
- snap.Release()
-
- h.reopenDB()
- h.getVal("foo", "v2")
- h.getVal(bar, "v2")
- })
-}
-
-func TestDB_GetLevel0Ordering(t *testing.T) {
- trun(t, func(h *dbHarness) {
- for i := 0; i < 4; i++ {
- h.put("bar", fmt.Sprintf("b%d", i))
- h.put("foo", fmt.Sprintf("v%d", i))
- h.compactMem()
- }
- h.getVal("foo", "v3")
- h.getVal("bar", "b3")
-
- v := h.db.s.version()
- t0len := v.tLen(0)
- v.release()
- if t0len < 2 {
- t.Errorf("level-0 tables is less than 2, got %d", t0len)
- }
-
- h.reopenDB()
- h.getVal("foo", "v3")
- h.getVal("bar", "b3")
- })
-}
-
-func TestDB_GetOrderedByLevels(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- h.compactMem()
- h.compactRange("a", "z")
- h.getVal("foo", "v1")
- h.put("foo", "v2")
- h.compactMem()
- h.getVal("foo", "v2")
- })
-}
-
-func TestDB_GetPicksCorrectFile(t *testing.T) {
- trun(t, func(h *dbHarness) {
- // Arrange to have multiple files in a non-level-0 level.
- h.put("a", "va")
- h.compactMem()
- h.compactRange("a", "b")
- h.put("x", "vx")
- h.compactMem()
- h.compactRange("x", "y")
- h.put("f", "vf")
- h.compactMem()
- h.compactRange("f", "g")
-
- h.getVal("a", "va")
- h.getVal("f", "vf")
- h.getVal("x", "vx")
-
- h.compactRange("", "")
- h.getVal("a", "va")
- h.getVal("f", "vf")
- h.getVal("x", "vx")
- })
-}
-
-func TestDB_GetEncountersEmptyLevel(t *testing.T) {
- trun(t, func(h *dbHarness) {
- // Arrange for the following to happen:
- // * sstable A in level 0
- // * nothing in level 1
- // * sstable B in level 2
- // Then do enough Get() calls to arrange for an automatic compaction
- // of sstable A. A bug would cause the compaction to be marked as
- // occuring at level 1 (instead of the correct level 0).
-
- // Step 1: First place sstables in levels 0 and 2
- for i := 0; ; i++ {
- if i >= 100 {
- t.Fatal("could not fill levels-0 and level-2")
- }
- v := h.db.s.version()
- if v.tLen(0) > 0 && v.tLen(2) > 0 {
- v.release()
- break
- }
- v.release()
- h.put("a", "begin")
- h.put("z", "end")
- h.compactMem()
-
- h.getVal("a", "begin")
- h.getVal("z", "end")
- }
-
- // Step 2: clear level 1 if necessary.
- h.compactRangeAt(1, "", "")
- h.tablesPerLevel("1,0,1")
-
- h.getVal("a", "begin")
- h.getVal("z", "end")
-
- // Step 3: read a bunch of times
- for i := 0; i < 200; i++ {
- h.get("missing", false)
- }
-
- // Step 4: Wait for compaction to finish
- h.waitCompaction()
-
- v := h.db.s.version()
- if v.tLen(0) > 0 {
- t.Errorf("level-0 tables more than 0, got %d", v.tLen(0))
- }
- v.release()
-
- h.getVal("a", "begin")
- h.getVal("z", "end")
- })
-}
-
-func TestDB_IterMultiWithDelete(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("a", "va")
- h.put("b", "vb")
- h.put("c", "vc")
- h.delete("b")
- h.get("b", false)
-
- iter := h.db.NewIterator(nil, nil)
- iter.Seek([]byte("c"))
- testKeyVal(t, iter, "c->vc")
- iter.Prev()
- testKeyVal(t, iter, "a->va")
- iter.Release()
-
- h.compactMem()
-
- iter = h.db.NewIterator(nil, nil)
- iter.Seek([]byte("c"))
- testKeyVal(t, iter, "c->vc")
- iter.Prev()
- testKeyVal(t, iter, "a->va")
- iter.Release()
- })
-}
-
-func TestDB_IteratorPinsRef(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.put("foo", "hello")
-
- // Get iterator that will yield the current contents of the DB.
- iter := h.db.NewIterator(nil, nil)
-
- // Write to force compactions
- h.put("foo", "newvalue1")
- for i := 0; i < 100; i++ {
- h.put(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), 100000/10))
- }
- h.put("foo", "newvalue2")
-
- iter.First()
- testKeyVal(t, iter, "foo->hello")
- if iter.Next() {
- t.Errorf("expect eof")
- }
- iter.Release()
-}
-
-func TestDB_Recover(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- h.put("baz", "v5")
-
- h.reopenDB()
- h.getVal("foo", "v1")
-
- h.getVal("foo", "v1")
- h.getVal("baz", "v5")
- h.put("bar", "v2")
- h.put("foo", "v3")
-
- h.reopenDB()
- h.getVal("foo", "v3")
- h.put("foo", "v4")
- h.getVal("foo", "v4")
- h.getVal("bar", "v2")
- h.getVal("baz", "v5")
- })
-}
-
-func TestDB_RecoverWithEmptyJournal(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- h.put("foo", "v2")
-
- h.reopenDB()
- h.reopenDB()
- h.put("foo", "v3")
-
- h.reopenDB()
- h.getVal("foo", "v3")
- })
-}
-
-func TestDB_RecoverDuringMemtableCompaction(t *testing.T) {
- truno(t, &opt.Options{WriteBuffer: 1000000}, func(h *dbHarness) {
-
- h.stor.DelaySync(storage.TypeTable)
- h.put("big1", strings.Repeat("x", 10000000))
- h.put("big2", strings.Repeat("y", 1000))
- h.put("bar", "v2")
- h.stor.ReleaseSync(storage.TypeTable)
-
- h.reopenDB()
- h.getVal("bar", "v2")
- h.getVal("big1", strings.Repeat("x", 10000000))
- h.getVal("big2", strings.Repeat("y", 1000))
- })
-}
-
-func TestDB_MinorCompactionsHappen(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 10000})
- defer h.close()
-
- n := 500
-
- key := func(i int) string {
- return fmt.Sprintf("key%06d", i)
- }
-
- for i := 0; i < n; i++ {
- h.put(key(i), key(i)+strings.Repeat("v", 1000))
- }
-
- for i := 0; i < n; i++ {
- h.getVal(key(i), key(i)+strings.Repeat("v", 1000))
- }
-
- h.reopenDB()
- for i := 0; i < n; i++ {
- h.getVal(key(i), key(i)+strings.Repeat("v", 1000))
- }
-}
-
-func TestDB_RecoverWithLargeJournal(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.put("big1", strings.Repeat("1", 200000))
- h.put("big2", strings.Repeat("2", 200000))
- h.put("small3", strings.Repeat("3", 10))
- h.put("small4", strings.Repeat("4", 10))
- h.tablesPerLevel("")
-
- // Make sure that if we re-open with a small write buffer size that
- // we flush table files in the middle of a large journal file.
- h.o.WriteBuffer = 100000
- h.reopenDB()
- h.getVal("big1", strings.Repeat("1", 200000))
- h.getVal("big2", strings.Repeat("2", 200000))
- h.getVal("small3", strings.Repeat("3", 10))
- h.getVal("small4", strings.Repeat("4", 10))
- v := h.db.s.version()
- if v.tLen(0) <= 1 {
- t.Errorf("tables-0 less than one")
- }
- v.release()
-}
-
-func TestDB_CompactionsGenerateMultipleFiles(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- WriteBuffer: 10000000,
- Compression: opt.NoCompression,
- })
- defer h.close()
-
- v := h.db.s.version()
- if v.tLen(0) > 0 {
- t.Errorf("level-0 tables more than 0, got %d", v.tLen(0))
- }
- v.release()
-
- n := 80
-
- // Write 8MB (80 values, each 100K)
- for i := 0; i < n; i++ {
- h.put(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), 100000/10))
- }
-
- // Reopening moves updates to level-0
- h.reopenDB()
- h.compactRangeAt(0, "", "")
-
- v = h.db.s.version()
- if v.tLen(0) > 0 {
- t.Errorf("level-0 tables more than 0, got %d", v.tLen(0))
- }
- if v.tLen(1) <= 1 {
- t.Errorf("level-1 tables less than 1, got %d", v.tLen(1))
- }
- v.release()
-
- for i := 0; i < n; i++ {
- h.getVal(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), 100000/10))
- }
-}
-
-func TestDB_RepeatedWritesToSameKey(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 100000})
- defer h.close()
-
- maxTables := h.o.GetNumLevel() + h.o.GetWriteL0PauseTrigger()
-
- value := strings.Repeat("v", 2*h.o.GetWriteBuffer())
- for i := 0; i < 5*maxTables; i++ {
- h.put("key", value)
- n := h.totalTables()
- if n > maxTables {
- t.Errorf("total tables exceed %d, got=%d, iter=%d", maxTables, n, i)
- }
- }
-}
-
-func TestDB_RepeatedWritesToSameKeyAfterReopen(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{WriteBuffer: 100000})
- defer h.close()
-
- h.reopenDB()
-
- maxTables := h.o.GetNumLevel() + h.o.GetWriteL0PauseTrigger()
-
- value := strings.Repeat("v", 2*h.o.GetWriteBuffer())
- for i := 0; i < 5*maxTables; i++ {
- h.put("key", value)
- n := h.totalTables()
- if n > maxTables {
- t.Errorf("total tables exceed %d, got=%d, iter=%d", maxTables, n, i)
- }
- }
-}
-
-func TestDB_SparseMerge(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression})
- defer h.close()
-
- h.putMulti(h.o.GetNumLevel(), "A", "Z")
-
- // Suppose there is:
- // small amount of data with prefix A
- // large amount of data with prefix B
- // small amount of data with prefix C
- // and that recent updates have made small changes to all three prefixes.
- // Check that we do not do a compaction that merges all of B in one shot.
- h.put("A", "va")
- value := strings.Repeat("x", 1000)
- for i := 0; i < 100000; i++ {
- h.put(fmt.Sprintf("B%010d", i), value)
- }
- h.put("C", "vc")
- h.compactMem()
- h.compactRangeAt(0, "", "")
- h.waitCompaction()
-
- // Make sparse update
- h.put("A", "va2")
- h.put("B100", "bvalue2")
- h.put("C", "vc2")
- h.compactMem()
-
- h.waitCompaction()
- h.maxNextLevelOverlappingBytes(20 * 1048576)
- h.compactRangeAt(0, "", "")
- h.waitCompaction()
- h.maxNextLevelOverlappingBytes(20 * 1048576)
- h.compactRangeAt(1, "", "")
- h.waitCompaction()
- h.maxNextLevelOverlappingBytes(20 * 1048576)
-}
-
-func TestDB_SizeOf(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- Compression: opt.NoCompression,
- WriteBuffer: 10000000,
- })
- defer h.close()
-
- h.sizeAssert("", "xyz", 0, 0)
- h.reopenDB()
- h.sizeAssert("", "xyz", 0, 0)
-
- // Write 8MB (80 values, each 100K)
- n := 80
- s1 := 100000
- s2 := 105000
-
- for i := 0; i < n; i++ {
- h.put(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), s1/10))
- }
-
- // 0 because SizeOf() does not account for memtable space
- h.sizeAssert("", numKey(50), 0, 0)
-
- for r := 0; r < 3; r++ {
- h.reopenDB()
-
- for cs := 0; cs < n; cs += 10 {
- for i := 0; i < n; i += 10 {
- h.sizeAssert("", numKey(i), uint64(s1*i), uint64(s2*i))
- h.sizeAssert("", numKey(i)+".suffix", uint64(s1*(i+1)), uint64(s2*(i+1)))
- h.sizeAssert(numKey(i), numKey(i+10), uint64(s1*10), uint64(s2*10))
- }
-
- h.sizeAssert("", numKey(50), uint64(s1*50), uint64(s2*50))
- h.sizeAssert("", numKey(50)+".suffix", uint64(s1*50), uint64(s2*50))
-
- h.compactRangeAt(0, numKey(cs), numKey(cs+9))
- }
-
- v := h.db.s.version()
- if v.tLen(0) != 0 {
- t.Errorf("level-0 tables was not zero, got %d", v.tLen(0))
- }
- if v.tLen(1) == 0 {
- t.Error("level-1 tables was zero")
- }
- v.release()
- }
-}
-
-func TestDB_SizeOf_MixOfSmallAndLarge(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression})
- defer h.close()
-
- sizes := []uint64{
- 10000,
- 10000,
- 100000,
- 10000,
- 100000,
- 10000,
- 300000,
- 10000,
- }
-
- for i, n := range sizes {
- h.put(numKey(i), strings.Repeat(fmt.Sprintf("v%09d", i), int(n)/10))
- }
-
- for r := 0; r < 3; r++ {
- h.reopenDB()
-
- var x uint64
- for i, n := range sizes {
- y := x
- if i > 0 {
- y += 1000
- }
- h.sizeAssert("", numKey(i), x, y)
- x += n
- }
-
- h.sizeAssert(numKey(3), numKey(5), 110000, 111000)
-
- h.compactRangeAt(0, "", "")
- }
-}
-
-func TestDB_Snapshot(t *testing.T) {
- trun(t, func(h *dbHarness) {
- h.put("foo", "v1")
- s1 := h.getSnapshot()
- h.put("foo", "v2")
- s2 := h.getSnapshot()
- h.put("foo", "v3")
- s3 := h.getSnapshot()
- h.put("foo", "v4")
-
- h.getValr(s1, "foo", "v1")
- h.getValr(s2, "foo", "v2")
- h.getValr(s3, "foo", "v3")
- h.getVal("foo", "v4")
-
- s3.Release()
- h.getValr(s1, "foo", "v1")
- h.getValr(s2, "foo", "v2")
- h.getVal("foo", "v4")
-
- s1.Release()
- h.getValr(s2, "foo", "v2")
- h.getVal("foo", "v4")
-
- s2.Release()
- h.getVal("foo", "v4")
- })
-}
-
-func TestDB_SnapshotList(t *testing.T) {
- db := &DB{snapsList: list.New()}
- e0a := db.acquireSnapshot()
- e0b := db.acquireSnapshot()
- db.seq = 1
- e1 := db.acquireSnapshot()
- db.seq = 2
- e2 := db.acquireSnapshot()
-
- if db.minSeq() != 0 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
- db.releaseSnapshot(e0a)
- if db.minSeq() != 0 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
- db.releaseSnapshot(e2)
- if db.minSeq() != 0 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
- db.releaseSnapshot(e0b)
- if db.minSeq() != 1 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
- e2 = db.acquireSnapshot()
- if db.minSeq() != 1 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
- db.releaseSnapshot(e1)
- if db.minSeq() != 2 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
- db.releaseSnapshot(e2)
- if db.minSeq() != 2 {
- t.Fatalf("invalid sequence number, got=%d", db.minSeq())
- }
-}
-
-func TestDB_HiddenValuesAreRemoved(t *testing.T) {
- trun(t, func(h *dbHarness) {
- s := h.db.s
-
- h.put("foo", "v1")
- h.compactMem()
- m := h.o.GetMaxMemCompationLevel()
- v := s.version()
- num := v.tLen(m)
- v.release()
- if num != 1 {
- t.Errorf("invalid level-%d len, want=1 got=%d", m, num)
- }
-
- // Place a table at level last-1 to prevent merging with preceding mutation
- h.put("a", "begin")
- h.put("z", "end")
- h.compactMem()
- v = s.version()
- if v.tLen(m) != 1 {
- t.Errorf("invalid level-%d len, want=1 got=%d", m, v.tLen(m))
- }
- if v.tLen(m-1) != 1 {
- t.Errorf("invalid level-%d len, want=1 got=%d", m-1, v.tLen(m-1))
- }
- v.release()
-
- h.delete("foo")
- h.put("foo", "v2")
- h.allEntriesFor("foo", "[ v2, DEL, v1 ]")
- h.compactMem()
- h.allEntriesFor("foo", "[ v2, DEL, v1 ]")
- h.compactRangeAt(m-2, "", "z")
- // DEL eliminated, but v1 remains because we aren't compacting that level
- // (DEL can be eliminated because v2 hides v1).
- h.allEntriesFor("foo", "[ v2, v1 ]")
- h.compactRangeAt(m-1, "", "")
- // Merging last-1 w/ last, so we are the base level for "foo", so
- // DEL is removed. (as is v1).
- h.allEntriesFor("foo", "[ v2 ]")
- })
-}
-
-func TestDB_DeletionMarkers2(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
- s := h.db.s
-
- h.put("foo", "v1")
- h.compactMem()
- m := h.o.GetMaxMemCompationLevel()
- v := s.version()
- num := v.tLen(m)
- v.release()
- if num != 1 {
- t.Errorf("invalid level-%d len, want=1 got=%d", m, num)
- }
-
- // Place a table at level last-1 to prevent merging with preceding mutation
- h.put("a", "begin")
- h.put("z", "end")
- h.compactMem()
- v = s.version()
- if v.tLen(m) != 1 {
- t.Errorf("invalid level-%d len, want=1 got=%d", m, v.tLen(m))
- }
- if v.tLen(m-1) != 1 {
- t.Errorf("invalid level-%d len, want=1 got=%d", m-1, v.tLen(m-1))
- }
- v.release()
-
- h.delete("foo")
- h.allEntriesFor("foo", "[ DEL, v1 ]")
- h.compactMem() // Moves to level last-2
- h.allEntriesFor("foo", "[ DEL, v1 ]")
- h.compactRangeAt(m-2, "", "")
- // DEL kept: "last" file overlaps
- h.allEntriesFor("foo", "[ DEL, v1 ]")
- h.compactRangeAt(m-1, "", "")
- // Merging last-1 w/ last, so we are the base level for "foo", so
- // DEL is removed. (as is v1).
- h.allEntriesFor("foo", "[ ]")
-}
-
-func TestDB_CompactionTableOpenError(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{OpenFilesCacheCapacity: -1})
- defer h.close()
-
- im := 10
- jm := 10
- for r := 0; r < 2; r++ {
- for i := 0; i < im; i++ {
- for j := 0; j < jm; j++ {
- h.put(fmt.Sprintf("k%d,%d", i, j), fmt.Sprintf("v%d,%d", i, j))
- }
- h.compactMem()
- }
- }
-
- if n := h.totalTables(); n != im*2 {
- t.Errorf("total tables is %d, want %d", n, im)
- }
-
- h.stor.SetEmuErr(storage.TypeTable, tsOpOpen)
- go h.db.CompactRange(util.Range{})
- if err := h.db.compSendIdle(h.db.tcompCmdC); err != nil {
- t.Log("compaction error: ", err)
- }
- h.closeDB0()
- h.openDB()
- h.stor.SetEmuErr(0, tsOpOpen)
-
- for i := 0; i < im; i++ {
- for j := 0; j < jm; j++ {
- h.getVal(fmt.Sprintf("k%d,%d", i, j), fmt.Sprintf("v%d,%d", i, j))
- }
- }
-}
-
-func TestDB_OverlapInLevel0(t *testing.T) {
- trun(t, func(h *dbHarness) {
- if h.o.GetMaxMemCompationLevel() != 2 {
- t.Fatal("fix test to reflect the config")
- }
-
- // Fill levels 1 and 2 to disable the pushing of new memtables to levels > 0.
- h.put("100", "v100")
- h.put("999", "v999")
- h.compactMem()
- h.delete("100")
- h.delete("999")
- h.compactMem()
- h.tablesPerLevel("0,1,1")
-
- // Make files spanning the following ranges in level-0:
- // files[0] 200 .. 900
- // files[1] 300 .. 500
- // Note that files are sorted by min key.
- h.put("300", "v300")
- h.put("500", "v500")
- h.compactMem()
- h.put("200", "v200")
- h.put("600", "v600")
- h.put("900", "v900")
- h.compactMem()
- h.tablesPerLevel("2,1,1")
-
- // Compact away the placeholder files we created initially
- h.compactRangeAt(1, "", "")
- h.compactRangeAt(2, "", "")
- h.tablesPerLevel("2")
-
- // Do a memtable compaction. Before bug-fix, the compaction would
- // not detect the overlap with level-0 files and would incorrectly place
- // the deletion in a deeper level.
- h.delete("600")
- h.compactMem()
- h.tablesPerLevel("3")
- h.get("600", false)
- })
-}
-
-func TestDB_L0_CompactionBug_Issue44_a(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.reopenDB()
- h.put("b", "v")
- h.reopenDB()
- h.delete("b")
- h.delete("a")
- h.reopenDB()
- h.delete("a")
- h.reopenDB()
- h.put("a", "v")
- h.reopenDB()
- h.reopenDB()
- h.getKeyVal("(a->v)")
- h.waitCompaction()
- h.getKeyVal("(a->v)")
-}
-
-func TestDB_L0_CompactionBug_Issue44_b(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.reopenDB()
- h.put("", "")
- h.reopenDB()
- h.delete("e")
- h.put("", "")
- h.reopenDB()
- h.put("c", "cv")
- h.reopenDB()
- h.put("", "")
- h.reopenDB()
- h.put("", "")
- h.waitCompaction()
- h.reopenDB()
- h.put("d", "dv")
- h.reopenDB()
- h.put("", "")
- h.reopenDB()
- h.delete("d")
- h.delete("b")
- h.reopenDB()
- h.getKeyVal("(->)(c->cv)")
- h.waitCompaction()
- h.getKeyVal("(->)(c->cv)")
-}
-
-func TestDB_SingleEntryMemCompaction(t *testing.T) {
- trun(t, func(h *dbHarness) {
- for i := 0; i < 10; i++ {
- h.put("big", strings.Repeat("v", opt.DefaultWriteBuffer))
- h.compactMem()
- h.put("key", strings.Repeat("v", opt.DefaultBlockSize))
- h.compactMem()
- h.put("k", "v")
- h.compactMem()
- h.put("", "")
- h.compactMem()
- h.put("verybig", strings.Repeat("v", opt.DefaultWriteBuffer*2))
- h.compactMem()
- }
- })
-}
-
-func TestDB_ManifestWriteError(t *testing.T) {
- for i := 0; i < 2; i++ {
- func() {
- h := newDbHarness(t)
- defer h.close()
-
- h.put("foo", "bar")
- h.getVal("foo", "bar")
-
- // Mem compaction (will succeed)
- h.compactMem()
- h.getVal("foo", "bar")
- v := h.db.s.version()
- if n := v.tLen(h.o.GetMaxMemCompationLevel()); n != 1 {
- t.Errorf("invalid total tables, want=1 got=%d", n)
- }
- v.release()
-
- if i == 0 {
- h.stor.SetEmuErr(storage.TypeManifest, tsOpWrite)
- } else {
- h.stor.SetEmuErr(storage.TypeManifest, tsOpSync)
- }
-
- // Merging compaction (will fail)
- h.compactRangeAtErr(h.o.GetMaxMemCompationLevel(), "", "", true)
-
- h.db.Close()
- h.stor.SetEmuErr(0, tsOpWrite)
- h.stor.SetEmuErr(0, tsOpSync)
-
- // Should not lose data
- h.openDB()
- h.getVal("foo", "bar")
- }()
- }
-}
-
-func assertErr(t *testing.T, err error, wanterr bool) {
- if err != nil {
- if wanterr {
- t.Log("AssertErr: got error (expected): ", err)
- } else {
- t.Error("AssertErr: got error: ", err)
- }
- } else if wanterr {
- t.Error("AssertErr: expect error")
- }
-}
-
-func TestDB_ClosedIsClosed(t *testing.T) {
- h := newDbHarness(t)
- db := h.db
-
- var iter, iter2 iterator.Iterator
- var snap *Snapshot
- func() {
- defer h.close()
-
- h.put("k", "v")
- h.getVal("k", "v")
-
- iter = db.NewIterator(nil, h.ro)
- iter.Seek([]byte("k"))
- testKeyVal(t, iter, "k->v")
-
- var err error
- snap, err = db.GetSnapshot()
- if err != nil {
- t.Fatal("GetSnapshot: got error: ", err)
- }
-
- h.getValr(snap, "k", "v")
-
- iter2 = snap.NewIterator(nil, h.ro)
- iter2.Seek([]byte("k"))
- testKeyVal(t, iter2, "k->v")
-
- h.put("foo", "v2")
- h.delete("foo")
-
- // closing DB
- iter.Release()
- iter2.Release()
- }()
-
- assertErr(t, db.Put([]byte("x"), []byte("y"), h.wo), true)
- _, err := db.Get([]byte("k"), h.ro)
- assertErr(t, err, true)
-
- if iter.Valid() {
- t.Errorf("iter.Valid should false")
- }
- assertErr(t, iter.Error(), false)
- testKeyVal(t, iter, "->")
- if iter.Seek([]byte("k")) {
- t.Errorf("iter.Seek should false")
- }
- assertErr(t, iter.Error(), true)
-
- assertErr(t, iter2.Error(), false)
-
- _, err = snap.Get([]byte("k"), h.ro)
- assertErr(t, err, true)
-
- _, err = db.GetSnapshot()
- assertErr(t, err, true)
-
- iter3 := db.NewIterator(nil, h.ro)
- assertErr(t, iter3.Error(), true)
-
- iter3 = snap.NewIterator(nil, h.ro)
- assertErr(t, iter3.Error(), true)
-
- assertErr(t, db.Delete([]byte("k"), h.wo), true)
-
- _, err = db.GetProperty("leveldb.stats")
- assertErr(t, err, true)
-
- _, err = db.SizeOf([]util.Range{{[]byte("a"), []byte("z")}})
- assertErr(t, err, true)
-
- assertErr(t, db.CompactRange(util.Range{}), true)
-
- assertErr(t, db.Close(), true)
-}
-
-type numberComparer struct{}
-
-func (numberComparer) num(x []byte) (n int) {
- fmt.Sscan(string(x[1:len(x)-1]), &n)
- return
-}
-
-func (numberComparer) Name() string {
- return "test.NumberComparer"
-}
-
-func (p numberComparer) Compare(a, b []byte) int {
- return p.num(a) - p.num(b)
-}
-
-func (numberComparer) Separator(dst, a, b []byte) []byte { return nil }
-func (numberComparer) Successor(dst, b []byte) []byte { return nil }
-
-func TestDB_CustomComparer(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- Comparer: numberComparer{},
- WriteBuffer: 1000,
- })
- defer h.close()
-
- h.put("[10]", "ten")
- h.put("[0x14]", "twenty")
- for i := 0; i < 2; i++ {
- h.getVal("[10]", "ten")
- h.getVal("[0xa]", "ten")
- h.getVal("[20]", "twenty")
- h.getVal("[0x14]", "twenty")
- h.get("[15]", false)
- h.get("[0xf]", false)
- h.compactMem()
- h.compactRange("[0]", "[9999]")
- }
-
- for n := 0; n < 2; n++ {
- for i := 0; i < 100; i++ {
- v := fmt.Sprintf("[%d]", i*10)
- h.put(v, v)
- }
- h.compactMem()
- h.compactRange("[0]", "[1000000]")
- }
-}
-
-func TestDB_ManualCompaction(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- if h.o.GetMaxMemCompationLevel() != 2 {
- t.Fatal("fix test to reflect the config")
- }
-
- h.putMulti(3, "p", "q")
- h.tablesPerLevel("1,1,1")
-
- // Compaction range falls before files
- h.compactRange("", "c")
- h.tablesPerLevel("1,1,1")
-
- // Compaction range falls after files
- h.compactRange("r", "z")
- h.tablesPerLevel("1,1,1")
-
- // Compaction range overlaps files
- h.compactRange("p1", "p9")
- h.tablesPerLevel("0,0,1")
-
- // Populate a different range
- h.putMulti(3, "c", "e")
- h.tablesPerLevel("1,1,2")
-
- // Compact just the new range
- h.compactRange("b", "f")
- h.tablesPerLevel("0,0,2")
-
- // Compact all
- h.putMulti(1, "a", "z")
- h.tablesPerLevel("0,1,2")
- h.compactRange("", "")
- h.tablesPerLevel("0,0,1")
-}
-
-func TestDB_BloomFilter(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- DisableBlockCache: true,
- Filter: filter.NewBloomFilter(10),
- })
- defer h.close()
-
- key := func(i int) string {
- return fmt.Sprintf("key%06d", i)
- }
-
- const n = 10000
-
- // Populate multiple layers
- for i := 0; i < n; i++ {
- h.put(key(i), key(i))
- }
- h.compactMem()
- h.compactRange("a", "z")
- for i := 0; i < n; i += 100 {
- h.put(key(i), key(i))
- }
- h.compactMem()
-
- // Prevent auto compactions triggered by seeks
- h.stor.DelaySync(storage.TypeTable)
-
- // Lookup present keys. Should rarely read from small sstable.
- h.stor.SetReadCounter(storage.TypeTable)
- for i := 0; i < n; i++ {
- h.getVal(key(i), key(i))
- }
- cnt := int(h.stor.ReadCounter())
- t.Logf("lookup of %d present keys yield %d sstable I/O reads", n, cnt)
-
- if min, max := n, n+2*n/100; cnt < min || cnt > max {
- t.Errorf("num of sstable I/O reads of present keys not in range of %d - %d, got %d", min, max, cnt)
- }
-
- // Lookup missing keys. Should rarely read from either sstable.
- h.stor.ResetReadCounter()
- for i := 0; i < n; i++ {
- h.get(key(i)+".missing", false)
- }
- cnt = int(h.stor.ReadCounter())
- t.Logf("lookup of %d missing keys yield %d sstable I/O reads", n, cnt)
- if max := 3 * n / 100; cnt > max {
- t.Errorf("num of sstable I/O reads of missing keys was more than %d, got %d", max, cnt)
- }
-
- h.stor.ReleaseSync(storage.TypeTable)
-}
-
-func TestDB_Concurrent(t *testing.T) {
- const n, secs, maxkey = 4, 2, 1000
-
- runtime.GOMAXPROCS(n)
- trun(t, func(h *dbHarness) {
- var closeWg sync.WaitGroup
- var stop uint32
- var cnt [n]uint32
-
- for i := 0; i < n; i++ {
- closeWg.Add(1)
- go func(i int) {
- var put, get, found uint
- defer func() {
- t.Logf("goroutine %d stopped after %d ops, put=%d get=%d found=%d missing=%d",
- i, cnt[i], put, get, found, get-found)
- closeWg.Done()
- }()
-
- rnd := rand.New(rand.NewSource(int64(1000 + i)))
- for atomic.LoadUint32(&stop) == 0 {
- x := cnt[i]
-
- k := rnd.Intn(maxkey)
- kstr := fmt.Sprintf("%016d", k)
-
- if (rnd.Int() % 2) > 0 {
- put++
- h.put(kstr, fmt.Sprintf("%d.%d.%-1000d", k, i, x))
- } else {
- get++
- v, err := h.db.Get([]byte(kstr), h.ro)
- if err == nil {
- found++
- rk, ri, rx := 0, -1, uint32(0)
- fmt.Sscanf(string(v), "%d.%d.%d", &rk, &ri, &rx)
- if rk != k {
- t.Errorf("invalid key want=%d got=%d", k, rk)
- }
- if ri < 0 || ri >= n {
- t.Error("invalid goroutine number: ", ri)
- } else {
- tx := atomic.LoadUint32(&(cnt[ri]))
- if rx > tx {
- t.Errorf("invalid seq number, %d > %d ", rx, tx)
- }
- }
- } else if err != ErrNotFound {
- t.Error("Get: got error: ", err)
- return
- }
- }
- atomic.AddUint32(&cnt[i], 1)
- }
- }(i)
- }
-
- time.Sleep(secs * time.Second)
- atomic.StoreUint32(&stop, 1)
- closeWg.Wait()
- })
-
- runtime.GOMAXPROCS(1)
-}
-
-func TestDB_Concurrent2(t *testing.T) {
- const n, n2 = 4, 4000
-
- runtime.GOMAXPROCS(n*2 + 2)
- truno(t, &opt.Options{WriteBuffer: 30}, func(h *dbHarness) {
- var closeWg sync.WaitGroup
- var stop uint32
-
- for i := 0; i < n; i++ {
- closeWg.Add(1)
- go func(i int) {
- for k := 0; atomic.LoadUint32(&stop) == 0; k++ {
- h.put(fmt.Sprintf("k%d", k), fmt.Sprintf("%d.%d.", k, i)+strings.Repeat("x", 10))
- }
- closeWg.Done()
- }(i)
- }
-
- for i := 0; i < n; i++ {
- closeWg.Add(1)
- go func(i int) {
- for k := 1000000; k < 0 || atomic.LoadUint32(&stop) == 0; k-- {
- h.put(fmt.Sprintf("k%d", k), fmt.Sprintf("%d.%d.", k, i)+strings.Repeat("x", 10))
- }
- closeWg.Done()
- }(i)
- }
-
- cmp := comparer.DefaultComparer
- for i := 0; i < n2; i++ {
- closeWg.Add(1)
- go func(i int) {
- it := h.db.NewIterator(nil, nil)
- var pk []byte
- for it.Next() {
- kk := it.Key()
- if cmp.Compare(kk, pk) <= 0 {
- t.Errorf("iter %d: %q is successor of %q", i, pk, kk)
- }
- pk = append(pk[:0], kk...)
- var k, vk, vi int
- if n, err := fmt.Sscanf(string(it.Key()), "k%d", &k); err != nil {
- t.Errorf("iter %d: Scanf error on key %q: %v", i, it.Key(), err)
- } else if n < 1 {
- t.Errorf("iter %d: Cannot parse key %q", i, it.Key())
- }
- if n, err := fmt.Sscanf(string(it.Value()), "%d.%d", &vk, &vi); err != nil {
- t.Errorf("iter %d: Scanf error on value %q: %v", i, it.Value(), err)
- } else if n < 2 {
- t.Errorf("iter %d: Cannot parse value %q", i, it.Value())
- }
-
- if vk != k {
- t.Errorf("iter %d: invalid value i=%d, want=%d got=%d", i, vi, k, vk)
- }
- }
- if err := it.Error(); err != nil {
- t.Errorf("iter %d: Got error: %v", i, err)
- }
- it.Release()
- closeWg.Done()
- }(i)
- }
-
- atomic.StoreUint32(&stop, 1)
- closeWg.Wait()
- })
-
- runtime.GOMAXPROCS(1)
-}
-
-func TestDB_CreateReopenDbOnFile(t *testing.T) {
- dbpath := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbtestCreateReopenDbOnFile-%d", os.Getuid()))
- if err := os.RemoveAll(dbpath); err != nil {
- t.Fatal("cannot remove old db: ", err)
- }
- defer os.RemoveAll(dbpath)
-
- for i := 0; i < 3; i++ {
- stor, err := storage.OpenFile(dbpath)
- if err != nil {
- t.Fatalf("(%d) cannot open storage: %s", i, err)
- }
- db, err := Open(stor, nil)
- if err != nil {
- t.Fatalf("(%d) cannot open db: %s", i, err)
- }
- if err := db.Put([]byte("foo"), []byte("bar"), nil); err != nil {
- t.Fatalf("(%d) cannot write to db: %s", i, err)
- }
- if err := db.Close(); err != nil {
- t.Fatalf("(%d) cannot close db: %s", i, err)
- }
- if err := stor.Close(); err != nil {
- t.Fatalf("(%d) cannot close storage: %s", i, err)
- }
- }
-}
-
-func TestDB_CreateReopenDbOnFile2(t *testing.T) {
- dbpath := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbtestCreateReopenDbOnFile2-%d", os.Getuid()))
- if err := os.RemoveAll(dbpath); err != nil {
- t.Fatal("cannot remove old db: ", err)
- }
- defer os.RemoveAll(dbpath)
-
- for i := 0; i < 3; i++ {
- db, err := OpenFile(dbpath, nil)
- if err != nil {
- t.Fatalf("(%d) cannot open db: %s", i, err)
- }
- if err := db.Put([]byte("foo"), []byte("bar"), nil); err != nil {
- t.Fatalf("(%d) cannot write to db: %s", i, err)
- }
- if err := db.Close(); err != nil {
- t.Fatalf("(%d) cannot close db: %s", i, err)
- }
- }
-}
-
-func TestDB_DeletionMarkersOnMemdb(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.put("foo", "v1")
- h.compactMem()
- h.delete("foo")
- h.get("foo", false)
- h.getKeyVal("")
-}
-
-func TestDB_LeveldbIssue178(t *testing.T) {
- nKeys := (opt.DefaultCompactionTableSize / 30) * 5
- key1 := func(i int) string {
- return fmt.Sprintf("my_key_%d", i)
- }
- key2 := func(i int) string {
- return fmt.Sprintf("my_key_%d_xxx", i)
- }
-
- // Disable compression since it affects the creation of layers and the
- // code below is trying to test against a very specific scenario.
- h := newDbHarnessWopt(t, &opt.Options{Compression: opt.NoCompression})
- defer h.close()
-
- // Create first key range.
- batch := new(Batch)
- for i := 0; i < nKeys; i++ {
- batch.Put([]byte(key1(i)), []byte("value for range 1 key"))
- }
- h.write(batch)
-
- // Create second key range.
- batch.Reset()
- for i := 0; i < nKeys; i++ {
- batch.Put([]byte(key2(i)), []byte("value for range 2 key"))
- }
- h.write(batch)
-
- // Delete second key range.
- batch.Reset()
- for i := 0; i < nKeys; i++ {
- batch.Delete([]byte(key2(i)))
- }
- h.write(batch)
- h.waitMemCompaction()
-
- // Run manual compaction.
- h.compactRange(key1(0), key1(nKeys-1))
-
- // Checking the keys.
- h.assertNumKeys(nKeys)
-}
-
-func TestDB_LeveldbIssue200(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- h.put("1", "b")
- h.put("2", "c")
- h.put("3", "d")
- h.put("4", "e")
- h.put("5", "f")
-
- iter := h.db.NewIterator(nil, h.ro)
-
- // Add an element that should not be reflected in the iterator.
- h.put("25", "cd")
-
- iter.Seek([]byte("5"))
- assertBytes(t, []byte("5"), iter.Key())
- iter.Prev()
- assertBytes(t, []byte("4"), iter.Key())
- iter.Prev()
- assertBytes(t, []byte("3"), iter.Key())
- iter.Next()
- assertBytes(t, []byte("4"), iter.Key())
- iter.Next()
- assertBytes(t, []byte("5"), iter.Key())
-}
-
-func TestDB_GoleveldbIssue74(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- WriteBuffer: 1 * opt.MiB,
- })
- defer h.close()
-
- const n, dur = 10000, 5 * time.Second
-
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- until := time.Now().Add(dur)
- wg := new(sync.WaitGroup)
- wg.Add(2)
- var done uint32
- go func() {
- var i int
- defer func() {
- t.Logf("WRITER DONE #%d", i)
- atomic.StoreUint32(&done, 1)
- wg.Done()
- }()
-
- b := new(Batch)
- for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ {
- iv := fmt.Sprintf("VAL%010d", i)
- for k := 0; k < n; k++ {
- key := fmt.Sprintf("KEY%06d", k)
- b.Put([]byte(key), []byte(key+iv))
- b.Put([]byte(fmt.Sprintf("PTR%06d", k)), []byte(key))
- }
- h.write(b)
-
- b.Reset()
- snap := h.getSnapshot()
- iter := snap.NewIterator(util.BytesPrefix([]byte("PTR")), nil)
- var k int
- for ; iter.Next(); k++ {
- ptrKey := iter.Key()
- key := iter.Value()
-
- if _, err := snap.Get(ptrKey, nil); err != nil {
- t.Fatalf("WRITER #%d snapshot.Get %q: %v", i, ptrKey, err)
- }
- if value, err := snap.Get(key, nil); err != nil {
- t.Fatalf("WRITER #%d snapshot.Get %q: %v", i, key, err)
- } else if string(value) != string(key)+iv {
- t.Fatalf("WRITER #%d snapshot.Get %q got invalid value, want %q got %q", i, key, string(key)+iv, value)
- }
-
- b.Delete(key)
- b.Delete(ptrKey)
- }
- h.write(b)
- iter.Release()
- snap.Release()
- if k != n {
- t.Fatalf("#%d %d != %d", i, k, n)
- }
- }
- }()
- go func() {
- var i int
- defer func() {
- t.Logf("READER DONE #%d", i)
- atomic.StoreUint32(&done, 1)
- wg.Done()
- }()
- for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ {
- snap := h.getSnapshot()
- iter := snap.NewIterator(util.BytesPrefix([]byte("PTR")), nil)
- var prevValue string
- var k int
- for ; iter.Next(); k++ {
- ptrKey := iter.Key()
- key := iter.Value()
-
- if _, err := snap.Get(ptrKey, nil); err != nil {
- t.Fatalf("READER #%d snapshot.Get %q: %v", i, ptrKey, err)
- }
-
- if value, err := snap.Get(key, nil); err != nil {
- t.Fatalf("READER #%d snapshot.Get %q: %v", i, key, err)
- } else if prevValue != "" && string(value) != string(key)+prevValue {
- t.Fatalf("READER #%d snapshot.Get %q got invalid value, want %q got %q", i, key, string(key)+prevValue, value)
- } else {
- prevValue = string(value[len(key):])
- }
- }
- iter.Release()
- snap.Release()
- if k > 0 && k != n {
- t.Fatalf("#%d %d != %d", i, k, n)
- }
- }
- }()
- wg.Wait()
-}
-
-func TestDB_GetProperties(t *testing.T) {
- h := newDbHarness(t)
- defer h.close()
-
- _, err := h.db.GetProperty("leveldb.num-files-at-level")
- if err == nil {
- t.Error("GetProperty() failed to detect missing level")
- }
-
- _, err = h.db.GetProperty("leveldb.num-files-at-level0")
- if err != nil {
- t.Error("got unexpected error", err)
- }
-
- _, err = h.db.GetProperty("leveldb.num-files-at-level0x")
- if err == nil {
- t.Error("GetProperty() failed to detect invalid level")
- }
-}
-
-func TestDB_GoleveldbIssue72and83(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- WriteBuffer: 1 * opt.MiB,
- OpenFilesCacheCapacity: 3,
- })
- defer h.close()
-
- const n, wn, dur = 10000, 100, 30 * time.Second
-
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- randomData := func(prefix byte, i int) []byte {
- data := make([]byte, 1+4+32+64+32)
- _, err := crand.Reader.Read(data[1 : len(data)-8])
- if err != nil {
- panic(err)
- }
- data[0] = prefix
- binary.LittleEndian.PutUint32(data[len(data)-8:], uint32(i))
- binary.LittleEndian.PutUint32(data[len(data)-4:], util.NewCRC(data[:len(data)-4]).Value())
- return data
- }
-
- keys := make([][]byte, n)
- for i := range keys {
- keys[i] = randomData(1, 0)
- }
-
- until := time.Now().Add(dur)
- wg := new(sync.WaitGroup)
- wg.Add(3)
- var done uint32
- go func() {
- i := 0
- defer func() {
- t.Logf("WRITER DONE #%d", i)
- wg.Done()
- }()
-
- b := new(Batch)
- for ; i < wn && atomic.LoadUint32(&done) == 0; i++ {
- b.Reset()
- for _, k1 := range keys {
- k2 := randomData(2, i)
- b.Put(k2, randomData(42, i))
- b.Put(k1, k2)
- }
- if err := h.db.Write(b, h.wo); err != nil {
- atomic.StoreUint32(&done, 1)
- t.Fatalf("WRITER #%d db.Write: %v", i, err)
- }
- }
- }()
- go func() {
- var i int
- defer func() {
- t.Logf("READER0 DONE #%d", i)
- atomic.StoreUint32(&done, 1)
- wg.Done()
- }()
- for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ {
- snap := h.getSnapshot()
- seq := snap.elem.seq
- if seq == 0 {
- snap.Release()
- continue
- }
- iter := snap.NewIterator(util.BytesPrefix([]byte{1}), nil)
- writei := int(seq/(n*2) - 1)
- var k int
- for ; iter.Next(); k++ {
- k1 := iter.Key()
- k2 := iter.Value()
- k1checksum0 := binary.LittleEndian.Uint32(k1[len(k1)-4:])
- k1checksum1 := util.NewCRC(k1[:len(k1)-4]).Value()
- if k1checksum0 != k1checksum1 {
- t.Fatalf("READER0 #%d.%d W#%d invalid K1 checksum: %#x != %#x", i, k, k1checksum0, k1checksum0)
- }
- k2checksum0 := binary.LittleEndian.Uint32(k2[len(k2)-4:])
- k2checksum1 := util.NewCRC(k2[:len(k2)-4]).Value()
- if k2checksum0 != k2checksum1 {
- t.Fatalf("READER0 #%d.%d W#%d invalid K2 checksum: %#x != %#x", i, k, k2checksum0, k2checksum1)
- }
- kwritei := int(binary.LittleEndian.Uint32(k2[len(k2)-8:]))
- if writei != kwritei {
- t.Fatalf("READER0 #%d.%d W#%d invalid write iteration num: %d", i, k, writei, kwritei)
- }
- if _, err := snap.Get(k2, nil); err != nil {
- t.Fatalf("READER0 #%d.%d W#%d snap.Get: %v\nk1: %x\n -> k2: %x", i, k, writei, err, k1, k2)
- }
- }
- if err := iter.Error(); err != nil {
- t.Fatalf("READER0 #%d.%d W#%d snap.Iterator: %v", i, k, writei, err)
- }
- iter.Release()
- snap.Release()
- if k > 0 && k != n {
- t.Fatalf("READER0 #%d W#%d short read, got=%d want=%d", i, writei, k, n)
- }
- }
- }()
- go func() {
- var i int
- defer func() {
- t.Logf("READER1 DONE #%d", i)
- atomic.StoreUint32(&done, 1)
- wg.Done()
- }()
- for ; time.Now().Before(until) && atomic.LoadUint32(&done) == 0; i++ {
- iter := h.db.NewIterator(nil, nil)
- seq := iter.(*dbIter).seq
- if seq == 0 {
- iter.Release()
- continue
- }
- writei := int(seq/(n*2) - 1)
- var k int
- for ok := iter.Last(); ok; ok = iter.Prev() {
- k++
- }
- if err := iter.Error(); err != nil {
- t.Fatalf("READER1 #%d.%d W#%d db.Iterator: %v", i, k, writei, err)
- }
- iter.Release()
- if m := (writei+1)*n + n; k != m {
- t.Fatalf("READER1 #%d W#%d short read, got=%d want=%d", i, writei, k, m)
- }
- }
- }()
-
- wg.Wait()
-}
-
-func TestDB_TransientError(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- WriteBuffer: 128 * opt.KiB,
- OpenFilesCacheCapacity: 3,
- DisableCompactionBackoff: true,
- })
- defer h.close()
-
- const (
- nSnap = 20
- nKey = 10000
- )
-
- var (
- snaps [nSnap]*Snapshot
- b = &Batch{}
- )
- for i := range snaps {
- vtail := fmt.Sprintf("VAL%030d", i)
- b.Reset()
- for k := 0; k < nKey; k++ {
- key := fmt.Sprintf("KEY%8d", k)
- b.Put([]byte(key), []byte(key+vtail))
- }
- h.stor.SetEmuRandErr(storage.TypeTable, tsOpOpen, tsOpRead, tsOpReadAt)
- if err := h.db.Write(b, nil); err != nil {
- t.Logf("WRITE #%d error: %v", i, err)
- h.stor.SetEmuRandErr(0, tsOpOpen, tsOpRead, tsOpReadAt, tsOpWrite)
- for {
- if err := h.db.Write(b, nil); err == nil {
- break
- } else if errors.IsCorrupted(err) {
- t.Fatalf("WRITE #%d corrupted: %v", i, err)
- }
- }
- }
-
- snaps[i] = h.db.newSnapshot()
- b.Reset()
- for k := 0; k < nKey; k++ {
- key := fmt.Sprintf("KEY%8d", k)
- b.Delete([]byte(key))
- }
- h.stor.SetEmuRandErr(storage.TypeTable, tsOpOpen, tsOpRead, tsOpReadAt)
- if err := h.db.Write(b, nil); err != nil {
- t.Logf("WRITE #%d error: %v", i, err)
- h.stor.SetEmuRandErr(0, tsOpOpen, tsOpRead, tsOpReadAt)
- for {
- if err := h.db.Write(b, nil); err == nil {
- break
- } else if errors.IsCorrupted(err) {
- t.Fatalf("WRITE #%d corrupted: %v", i, err)
- }
- }
- }
- }
- h.stor.SetEmuRandErr(0, tsOpOpen, tsOpRead, tsOpReadAt)
-
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- rnd := rand.New(rand.NewSource(0xecafdaed))
- wg := &sync.WaitGroup{}
- for i, snap := range snaps {
- wg.Add(2)
-
- go func(i int, snap *Snapshot, sk []int) {
- defer wg.Done()
-
- vtail := fmt.Sprintf("VAL%030d", i)
- for _, k := range sk {
- key := fmt.Sprintf("KEY%8d", k)
- xvalue, err := snap.Get([]byte(key), nil)
- if err != nil {
- t.Fatalf("READER_GET #%d SEQ=%d K%d error: %v", i, snap.elem.seq, k, err)
- }
- value := key + vtail
- if !bytes.Equal([]byte(value), xvalue) {
- t.Fatalf("READER_GET #%d SEQ=%d K%d invalid value: want %q, got %q", i, snap.elem.seq, k, value, xvalue)
- }
- }
- }(i, snap, rnd.Perm(nKey))
-
- go func(i int, snap *Snapshot) {
- defer wg.Done()
-
- vtail := fmt.Sprintf("VAL%030d", i)
- iter := snap.NewIterator(nil, nil)
- defer iter.Release()
- for k := 0; k < nKey; k++ {
- if !iter.Next() {
- if err := iter.Error(); err != nil {
- t.Fatalf("READER_ITER #%d K%d error: %v", i, k, err)
- } else {
- t.Fatalf("READER_ITER #%d K%d eoi", i, k)
- }
- }
- key := fmt.Sprintf("KEY%8d", k)
- xkey := iter.Key()
- if !bytes.Equal([]byte(key), xkey) {
- t.Fatalf("READER_ITER #%d K%d invalid key: want %q, got %q", i, k, key, xkey)
- }
- value := key + vtail
- xvalue := iter.Value()
- if !bytes.Equal([]byte(value), xvalue) {
- t.Fatalf("READER_ITER #%d K%d invalid value: want %q, got %q", i, k, value, xvalue)
- }
- }
- }(i, snap)
- }
-
- wg.Wait()
-}
-
-func TestDB_UkeyShouldntHopAcrossTable(t *testing.T) {
- h := newDbHarnessWopt(t, &opt.Options{
- WriteBuffer: 112 * opt.KiB,
- CompactionTableSize: 90 * opt.KiB,
- CompactionExpandLimitFactor: 1,
- })
- defer h.close()
-
- const (
- nSnap = 190
- nKey = 140
- )
-
- var (
- snaps [nSnap]*Snapshot
- b = &Batch{}
- )
- for i := range snaps {
- vtail := fmt.Sprintf("VAL%030d", i)
- b.Reset()
- for k := 0; k < nKey; k++ {
- key := fmt.Sprintf("KEY%08d", k)
- b.Put([]byte(key), []byte(key+vtail))
- }
- if err := h.db.Write(b, nil); err != nil {
- t.Fatalf("WRITE #%d error: %v", i, err)
- }
-
- snaps[i] = h.db.newSnapshot()
- b.Reset()
- for k := 0; k < nKey; k++ {
- key := fmt.Sprintf("KEY%08d", k)
- b.Delete([]byte(key))
- }
- if err := h.db.Write(b, nil); err != nil {
- t.Fatalf("WRITE #%d error: %v", i, err)
- }
- }
-
- h.compactMem()
-
- h.waitCompaction()
- for level, tables := range h.db.s.stVersion.tables {
- for _, table := range tables {
- t.Logf("L%d@%d %q:%q", level, table.file.Num(), table.imin, table.imax)
- }
- }
-
- h.compactRangeAt(0, "", "")
- h.waitCompaction()
- for level, tables := range h.db.s.stVersion.tables {
- for _, table := range tables {
- t.Logf("L%d@%d %q:%q", level, table.file.Num(), table.imin, table.imax)
- }
- }
- h.compactRangeAt(1, "", "")
- h.waitCompaction()
- for level, tables := range h.db.s.stVersion.tables {
- for _, table := range tables {
- t.Logf("L%d@%d %q:%q", level, table.file.Num(), table.imin, table.imax)
- }
- }
- runtime.GOMAXPROCS(runtime.NumCPU())
-
- wg := &sync.WaitGroup{}
- for i, snap := range snaps {
- wg.Add(1)
-
- go func(i int, snap *Snapshot) {
- defer wg.Done()
-
- vtail := fmt.Sprintf("VAL%030d", i)
- for k := 0; k < nKey; k++ {
- key := fmt.Sprintf("KEY%08d", k)
- xvalue, err := snap.Get([]byte(key), nil)
- if err != nil {
- t.Fatalf("READER_GET #%d SEQ=%d K%d error: %v", i, snap.elem.seq, k, err)
- }
- value := key + vtail
- if !bytes.Equal([]byte(value), xvalue) {
- t.Fatalf("READER_GET #%d SEQ=%d K%d invalid value: want %q, got %q", i, snap.elem.seq, k, value, xvalue)
- }
- }
- }(i, snap)
- }
-
- wg.Wait()
-}
-
-func TestDB_TableCompactionBuilder(t *testing.T) {
- stor := newTestStorage(t)
- defer stor.Close()
-
- const nSeq = 99
-
- o := &opt.Options{
- WriteBuffer: 112 * opt.KiB,
- CompactionTableSize: 43 * opt.KiB,
- CompactionExpandLimitFactor: 1,
- CompactionGPOverlapsFactor: 1,
- DisableBlockCache: true,
- }
- s, err := newSession(stor, o)
- if err != nil {
- t.Fatal(err)
- }
- if err := s.create(); err != nil {
- t.Fatal(err)
- }
- defer s.close()
- var (
- seq uint64
- targetSize = 5 * o.CompactionTableSize
- value = bytes.Repeat([]byte{'0'}, 100)
- )
- for i := 0; i < 2; i++ {
- tw, err := s.tops.create()
- if err != nil {
- t.Fatal(err)
- }
- for k := 0; tw.tw.BytesLen() < targetSize; k++ {
- key := []byte(fmt.Sprintf("%09d", k))
- seq += nSeq - 1
- for x := uint64(0); x < nSeq; x++ {
- if err := tw.append(newIkey(key, seq-x, ktVal), value); err != nil {
- t.Fatal(err)
- }
- }
- }
- tf, err := tw.finish()
- if err != nil {
- t.Fatal(err)
- }
- rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
- rec.addTableFile(i, tf)
- if err := s.commit(rec); err != nil {
- t.Fatal(err)
- }
- }
-
- // Build grandparent.
- v := s.version()
- c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
- rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
- b := &tableCompactionBuilder{
- s: s,
- c: c,
- rec: rec,
- stat1: new(cStatsStaging),
- minSeq: 0,
- strict: true,
- tableSize: o.CompactionTableSize/3 + 961,
- }
- if err := b.run(new(compactionTransactCounter)); err != nil {
- t.Fatal(err)
- }
- for _, t := range c.tables[0] {
- rec.delTable(c.level, t.file.Num())
- }
- if err := s.commit(rec); err != nil {
- t.Fatal(err)
- }
- c.release()
-
- // Build level-1.
- v = s.version()
- c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...))
- rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
- b = &tableCompactionBuilder{
- s: s,
- c: c,
- rec: rec,
- stat1: new(cStatsStaging),
- minSeq: 0,
- strict: true,
- tableSize: o.CompactionTableSize,
- }
- if err := b.run(new(compactionTransactCounter)); err != nil {
- t.Fatal(err)
- }
- for _, t := range c.tables[0] {
- rec.delTable(c.level, t.file.Num())
- }
- // Move grandparent to level-3
- for _, t := range v.tables[2] {
- rec.delTable(2, t.file.Num())
- rec.addTableFile(3, t)
- }
- if err := s.commit(rec); err != nil {
- t.Fatal(err)
- }
- c.release()
-
- v = s.version()
- for level, want := range []bool{false, true, false, true, false} {
- got := len(v.tables[level]) > 0
- if want != got {
- t.Fatalf("invalid level-%d tables len: want %v, got %v", level, want, got)
- }
- }
- for i, f := range v.tables[1][:len(v.tables[1])-1] {
- nf := v.tables[1][i+1]
- if bytes.Equal(f.imax.ukey(), nf.imin.ukey()) {
- t.Fatalf("KEY %q hop across table %d .. %d", f.imax.ukey(), f.file.Num(), nf.file.Num())
- }
- }
- v.release()
-
- // Compaction with transient error.
- v = s.version()
- c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
- rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
- b = &tableCompactionBuilder{
- s: s,
- c: c,
- rec: rec,
- stat1: new(cStatsStaging),
- minSeq: 0,
- strict: true,
- tableSize: o.CompactionTableSize,
- }
- stor.SetEmuErrOnce(storage.TypeTable, tsOpSync)
- stor.SetEmuRandErr(storage.TypeTable, tsOpRead, tsOpReadAt, tsOpWrite)
- stor.SetEmuRandErrProb(0xf0)
- for {
- if err := b.run(new(compactionTransactCounter)); err != nil {
- t.Logf("(expected) b.run: %v", err)
- } else {
- break
- }
- }
- if err := s.commit(rec); err != nil {
- t.Fatal(err)
- }
- c.release()
-
- stor.SetEmuErrOnce(0, tsOpSync)
- stor.SetEmuRandErr(0, tsOpRead, tsOpReadAt, tsOpWrite)
-
- v = s.version()
- if len(v.tables[1]) != len(v.tables[2]) {
- t.Fatalf("invalid tables length, want %d, got %d", len(v.tables[1]), len(v.tables[2]))
- }
- for i, f0 := range v.tables[1] {
- f1 := v.tables[2][i]
- iter0 := s.tops.newIterator(f0, nil, nil)
- iter1 := s.tops.newIterator(f1, nil, nil)
- for j := 0; true; j++ {
- next0 := iter0.Next()
- next1 := iter1.Next()
- if next0 != next1 {
- t.Fatalf("#%d.%d invalid eoi: want %v, got %v", i, j, next0, next1)
- }
- key0 := iter0.Key()
- key1 := iter1.Key()
- if !bytes.Equal(key0, key1) {
- t.Fatalf("#%d.%d invalid key: want %q, got %q", i, j, key0, key1)
- }
- if next0 == false {
- break
- }
- }
- iter0.Release()
- iter1.Release()
- }
- v.release()
-}
-
-func testDB_IterTriggeredCompaction(t *testing.T, limitDiv int) {
- const (
- vSize = 200 * opt.KiB
- tSize = 100 * opt.MiB
- mIter = 100
- n = tSize / vSize
- )
-
- h := newDbHarnessWopt(t, &opt.Options{
- Compression: opt.NoCompression,
- DisableBlockCache: true,
- })
- defer h.close()
-
- key := func(x int) string {
- return fmt.Sprintf("v%06d", x)
- }
-
- // Fill.
- value := strings.Repeat("x", vSize)
- for i := 0; i < n; i++ {
- h.put(key(i), value)
- }
- h.compactMem()
-
- // Delete all.
- for i := 0; i < n; i++ {
- h.delete(key(i))
- }
- h.compactMem()
-
- var (
- limit = n / limitDiv
-
- startKey = key(0)
- limitKey = key(limit)
- maxKey = key(n)
- slice = &util.Range{Limit: []byte(limitKey)}
-
- initialSize0 = h.sizeOf(startKey, limitKey)
- initialSize1 = h.sizeOf(limitKey, maxKey)
- )
-
- t.Logf("inital size %s [rest %s]", shortenb(int(initialSize0)), shortenb(int(initialSize1)))
-
- for r := 0; true; r++ {
- if r >= mIter {
- t.Fatal("taking too long to compact")
- }
-
- // Iterates.
- iter := h.db.NewIterator(slice, h.ro)
- for iter.Next() {
- }
- if err := iter.Error(); err != nil {
- t.Fatalf("Iter err: %v", err)
- }
- iter.Release()
-
- // Wait compaction.
- h.waitCompaction()
-
- // Check size.
- size0 := h.sizeOf(startKey, limitKey)
- size1 := h.sizeOf(limitKey, maxKey)
- t.Logf("#%03d size %s [rest %s]", r, shortenb(int(size0)), shortenb(int(size1)))
- if size0 < initialSize0/10 {
- break
- }
- }
-
- if initialSize1 > 0 {
- h.sizeAssert(limitKey, maxKey, initialSize1/4-opt.MiB, initialSize1+opt.MiB)
- }
-}
-
-func TestDB_IterTriggeredCompaction(t *testing.T) {
- testDB_IterTriggeredCompaction(t, 1)
-}
-
-func TestDB_IterTriggeredCompactionHalf(t *testing.T) {
- testDB_IterTriggeredCompaction(t, 2)
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_transaction.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_transaction.go
new file mode 100644
index 000000000..527028756
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_transaction.go
@@ -0,0 +1,289 @@
+// Copyright (c) 2016, Suryandaru Triandana <syndtr@gmail.com>
+// All rights reserved.
+//
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package leveldb
+
+import (
+ "errors"
+ "sync"
+ "time"
+
+ "github.com/syndtr/goleveldb/leveldb/iterator"
+ "github.com/syndtr/goleveldb/leveldb/opt"
+ "github.com/syndtr/goleveldb/leveldb/util"
+)
+
+var errTransactionDone = errors.New("leveldb: transaction already closed")
+
+// Transaction is the transaction handle.
+type Transaction struct {
+ db *DB
+ lk sync.RWMutex
+ seq uint64
+ mem *memDB
+ tables tFiles
+ ikScratch []byte
+ rec sessionRecord
+ stats cStatStaging
+ closed bool
+}
+
+// Get gets the value for the given key. It returns ErrNotFound if the
+// DB does not contains the key.
+//
+// The returned slice is its own copy, it is safe to modify the contents
+// of the returned slice.
+// It is safe to modify the contents of the argument after Get returns.
+func (tr *Transaction) Get(key []byte, ro *opt.ReadOptions) ([]byte, error) {
+ tr.lk.RLock()
+ defer tr.lk.RUnlock()
+ if tr.closed {
+ return nil, errTransactionDone
+ }
+ return tr.db.get(tr.mem.DB, tr.tables, key, tr.seq, ro)
+}
+
+// Has returns true if the DB does contains the given key.
+//
+// It is safe to modify the contents of the argument after Has returns.
+func (tr *Transaction) Has(key []byte, ro *opt.ReadOptions) (bool, error) {
+ tr.lk.RLock()
+ defer tr.lk.RUnlock()
+ if tr.closed {
+ return false, errTransactionDone
+ }
+ return tr.db.has(tr.mem.DB, tr.tables, key, tr.seq, ro)
+}
+
+// NewIterator returns an iterator for the latest snapshot of the transaction.
+// The returned iterator is not goroutine-safe, but it is safe to use multiple
+// iterators concurrently, with each in a dedicated goroutine.
+// It is also safe to use an iterator concurrently while writes to the
+// transaction. The resultant key/value pairs are guaranteed to be consistent.
+//
+// Slice allows slicing the iterator to only contains keys in the given
+// range. A nil Range.Start is treated as a key before all keys in the
+// DB. And a nil Range.Limit is treated as a key after all keys in
+// the DB.
+//
+// The iterator must be released after use, by calling Release method.
+//
+// Also read Iterator documentation of the leveldb/iterator package.
+func (tr *Transaction) NewIterator(slice *util.Range, ro *opt.ReadOptions) iterator.Iterator {
+ tr.lk.RLock()
+ defer tr.lk.RUnlock()
+ if tr.closed {
+ return iterator.NewEmptyIterator(errTransactionDone)
+ }
+ tr.mem.incref()
+ return tr.db.newIterator(tr.mem, tr.tables, tr.seq, slice, ro)
+}
+
+func (tr *Transaction) flush() error {
+ // Flush memdb.
+ if tr.mem.Len() != 0 {
+ tr.stats.startTimer()
+ iter := tr.mem.NewIterator(nil)
+ t, n, err := tr.db.s.tops.createFrom(iter)
+ iter.Release()
+ tr.stats.stopTimer()
+ if err != nil {
+ return err
+ }
+ if tr.mem.getref() == 1 {
+ tr.mem.Reset()
+ } else {
+ tr.mem.decref()
+ tr.mem = tr.db.mpoolGet(0)
+ tr.mem.incref()
+ }
+ tr.tables = append(tr.tables, t)
+ tr.rec.addTableFile(0, t)
+ tr.stats.write += t.size
+ tr.db.logf("transaction@flush created L0@%d N·%d S·%s %q:%q", t.fd.Num, n, shortenb(int(t.size)), t.imin, t.imax)
+ }
+ return nil
+}
+
+func (tr *Transaction) put(kt kType, key, value []byte) error {
+ tr.ikScratch = makeIkey(tr.ikScratch, key, tr.seq+1, kt)
+ if tr.mem.Free() < len(tr.ikScratch)+len(value) {
+ if err := tr.flush(); err != nil {
+ return err
+ }
+ }
+ if err := tr.mem.Put(tr.ikScratch, value); err != nil {
+ return err
+ }
+ tr.seq++
+ return nil
+}
+
+// Put sets the value for the given key. It overwrites any previous value
+// for that key; a DB is not a multi-map.
+// Please note that the transaction is not compacted until committed, so if you
+// writes 10 same keys, then those 10 same keys are in the transaction.
+//
+// It is safe to modify the contents of the arguments after Put returns.
+func (tr *Transaction) Put(key, value []byte, wo *opt.WriteOptions) error {
+ tr.lk.Lock()
+ defer tr.lk.Unlock()
+ if tr.closed {
+ return errTransactionDone
+ }
+ return tr.put(ktVal, key, value)
+}
+
+// Delete deletes the value for the given key.
+// Please note that the transaction is not compacted until committed, so if you
+// writes 10 same keys, then those 10 same keys are in the transaction.
+//
+// It is safe to modify the contents of the arguments after Delete returns.
+func (tr *Transaction) Delete(key []byte, wo *opt.WriteOptions) error {
+ tr.lk.Lock()
+ defer tr.lk.Unlock()
+ if tr.closed {
+ return errTransactionDone
+ }
+ return tr.put(ktDel, key, nil)
+}
+
+// Write apply the given batch to the transaction. The batch will be applied
+// sequentially.
+// Please note that the transaction is not compacted until committed, so if you
+// writes 10 same keys, then those 10 same keys are in the transaction.
+//
+// It is safe to modify the contents of the arguments after Write returns.
+func (tr *Transaction) Write(b *Batch, wo *opt.WriteOptions) error {
+ if b == nil || b.Len() == 0 {
+ return nil
+ }
+
+ tr.lk.Lock()
+ defer tr.lk.Unlock()
+ if tr.closed {
+ return errTransactionDone
+ }
+ return b.decodeRec(func(i int, kt kType, key, value []byte) error {
+ return tr.put(kt, key, value)
+ })
+}
+
+func (tr *Transaction) setDone() {
+ tr.closed = true
+ tr.db.tr = nil
+ tr.mem.decref()
+ <-tr.db.writeLockC
+}
+
+// Commit commits the transaction.
+//
+// Other methods should not be called after transaction has been committed.
+func (tr *Transaction) Commit() error {
+ if err := tr.db.ok(); err != nil {
+ return err
+ }
+
+ tr.lk.Lock()
+ defer tr.lk.Unlock()
+ if tr.closed {
+ return errTransactionDone
+ }
+ defer tr.setDone()
+ if err := tr.flush(); err != nil {
+ tr.discard()
+ return err
+ }
+ if len(tr.tables) != 0 {
+ // Committing transaction.
+ tr.rec.setSeqNum(tr.seq)
+ tr.db.compCommitLk.Lock()
+ defer tr.db.compCommitLk.Unlock()
+ for retry := 0; retry < 3; retry++ {
+ if err := tr.db.s.commit(&tr.rec); err != nil {
+ tr.db.logf("transaction@commit error R·%d %q", retry, err)
+ select {
+ case <-time.After(time.Second):
+ case _, _ = <-tr.db.closeC:
+ tr.db.logf("transaction@commit exiting")
+ return err
+ }
+ } else {
+ // Success. Set db.seq.
+ tr.db.setSeq(tr.seq)
+ break
+ }
+ }
+ // Trigger table auto-compaction.
+ tr.db.compTrigger(tr.db.tcompCmdC)
+ }
+ return nil
+}
+
+func (tr *Transaction) discard() {
+ // Discard transaction.
+ for _, t := range tr.tables {
+ tr.db.logf("transaction@discard @%d", t.fd.Num)
+ if err1 := tr.db.s.stor.Remove(t.fd); err1 == nil {
+ tr.db.s.reuseFileNum(t.fd.Num)
+ }
+ }
+}
+
+// Discard discards the transaction.
+//
+// Other methods should not be called after transaction has been discarded.
+func (tr *Transaction) Discard() {
+ tr.lk.Lock()
+ if !tr.closed {
+ tr.discard()
+ tr.setDone()
+ }
+ tr.lk.Unlock()
+}
+
+// OpenTransaction opens an atomic DB transaction. Only one transaction can be
+// opened at a time. Write will be blocked until the transaction is committed or
+// discarded.
+// The returned transaction handle is goroutine-safe.
+//
+// The transaction must be closed once done, either by committing or discarding
+// the transaction.
+// Closing the DB will discard open transaction.
+func (db *DB) OpenTransaction() (*Transaction, error) {
+ if err := db.ok(); err != nil {
+ return nil, err
+ }
+
+ // The write happen synchronously.
+ select {
+ case db.writeLockC <- struct{}{}:
+ case err := <-db.compPerErrC:
+ return nil, err
+ case _, _ = <-db.closeC:
+ return nil, ErrClosed
+ }
+
+ if db.tr != nil {
+ panic("leveldb: has open transaction")
+ }
+
+ // Flush current memdb.
+ if db.mem != nil && db.mem.Len() != 0 {
+ if _, err := db.rotateMem(0, true); err != nil {
+ return nil, err
+ }
+ }
+
+ tr := &Transaction{
+ db: db,
+ seq: db.seq,
+ mem: db.mpoolGet(0),
+ }
+ tr.mem.incref()
+ db.tr = tr
+ return tr, nil
+}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_util.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_util.go
index a8a2bdf72..8ec86b2ac 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_util.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_util.go
@@ -40,59 +40,59 @@ func (db *DB) checkAndCleanFiles() error {
v := db.s.version()
defer v.release()
- tablesMap := make(map[uint64]bool)
- for _, tables := range v.tables {
+ tmap := make(map[int64]bool)
+ for _, tables := range v.levels {
for _, t := range tables {
- tablesMap[t.file.Num()] = false
+ tmap[t.fd.Num] = false
}
}
- files, err := db.s.getFiles(storage.TypeAll)
+ fds, err := db.s.stor.List(storage.TypeAll)
if err != nil {
return err
}
- var nTables int
- var rem []storage.File
- for _, f := range files {
+ var nt int
+ var rem []storage.FileDesc
+ for _, fd := range fds {
keep := true
- switch f.Type() {
+ switch fd.Type {
case storage.TypeManifest:
- keep = f.Num() >= db.s.manifestFile.Num()
+ keep = fd.Num >= db.s.manifestFd.Num
case storage.TypeJournal:
- if db.frozenJournalFile != nil {
- keep = f.Num() >= db.frozenJournalFile.Num()
+ if !db.frozenJournalFd.Nil() {
+ keep = fd.Num >= db.frozenJournalFd.Num
} else {
- keep = f.Num() >= db.journalFile.Num()
+ keep = fd.Num >= db.journalFd.Num
}
case storage.TypeTable:
- _, keep = tablesMap[f.Num()]
+ _, keep = tmap[fd.Num]
if keep {
- tablesMap[f.Num()] = true
- nTables++
+ tmap[fd.Num] = true
+ nt++
}
}
if !keep {
- rem = append(rem, f)
+ rem = append(rem, fd)
}
}
- if nTables != len(tablesMap) {
- var missing []*storage.FileInfo
- for num, present := range tablesMap {
+ if nt != len(tmap) {
+ var mfds []storage.FileDesc
+ for num, present := range tmap {
if !present {
- missing = append(missing, &storage.FileInfo{Type: storage.TypeTable, Num: num})
+ mfds = append(mfds, storage.FileDesc{storage.TypeTable, num})
db.logf("db@janitor table missing @%d", num)
}
}
- return errors.NewErrCorrupted(nil, &errors.ErrMissingFiles{Files: missing})
+ return errors.NewErrCorrupted(storage.FileDesc{}, &errors.ErrMissingFiles{Fds: mfds})
}
- db.logf("db@janitor F·%d G·%d", len(files), len(rem))
- for _, f := range rem {
- db.logf("db@janitor removing %s-%d", f.Type(), f.Num())
- if err := f.Remove(); err != nil {
+ db.logf("db@janitor F·%d G·%d", len(fds), len(rem))
+ for _, fd := range rem {
+ db.logf("db@janitor removing %s-%d", fd.Type, fd.Num)
+ if err := db.s.stor.Remove(fd); err != nil {
return err
}
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_write.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_write.go
index e1cf30c53..5200be6fc 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_write.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_write.go
@@ -45,9 +45,9 @@ func (db *DB) jWriter() {
}
}
-func (db *DB) rotateMem(n int) (mem *memDB, err error) {
+func (db *DB) rotateMem(n int, wait bool) (mem *memDB, err error) {
// Wait for pending memdb compaction.
- err = db.compSendIdle(db.mcompCmdC)
+ err = db.compTriggerWait(db.mcompCmdC)
if err != nil {
return
}
@@ -59,46 +59,50 @@ func (db *DB) rotateMem(n int) (mem *memDB, err error) {
}
// Schedule memdb compaction.
- db.compSendTrigger(db.mcompCmdC)
+ if wait {
+ err = db.compTriggerWait(db.mcompCmdC)
+ } else {
+ db.compTrigger(db.mcompCmdC)
+ }
return
}
-func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
+func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
delayed := false
flush := func() (retry bool) {
v := db.s.version()
defer v.release()
- mem = db.getEffectiveMem()
+ mdb = db.getEffectiveMem()
defer func() {
if retry {
- mem.decref()
- mem = nil
+ mdb.decref()
+ mdb = nil
}
}()
- nn = mem.mdb.Free()
+ mdbFree = mdb.Free()
switch {
case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed:
delayed = true
time.Sleep(time.Millisecond)
- case nn >= n:
+ case mdbFree >= n:
return false
case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger():
delayed = true
- err = db.compSendIdle(db.tcompCmdC)
+ err = db.compTriggerWait(db.tcompCmdC)
if err != nil {
return false
}
default:
// Allow memdb to grow if it has no entry.
- if mem.mdb.Len() == 0 {
- nn = n
+ if mdb.Len() == 0 {
+ mdbFree = n
} else {
- mem.decref()
- mem, err = db.rotateMem(n)
+ mdb.decref()
+ mdb, err = db.rotateMem(n, false)
if err == nil {
- nn = mem.mdb.Free()
+ mdbFree = mdb.Free()
} else {
- nn = 0
+ mdbFree = 0
}
}
return false
@@ -129,7 +133,20 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
return
}
- b.init(wo.GetSync())
+ b.init(wo.GetSync() && !db.s.o.GetNoSync())
+
+ if b.size() > db.s.o.GetWriteBuffer() && !db.s.o.GetDisableLargeBatchTransaction() {
+ // Writes using transaction.
+ tr, err1 := db.OpenTransaction()
+ if err1 != nil {
+ return err1
+ }
+ if err1 := tr.Write(b, wo); err1 != nil {
+ tr.Discard()
+ return err1
+ }
+ return tr.Commit()
+ }
// The write happen synchronously.
select {
@@ -137,6 +154,8 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
if <-db.writeMergedC {
return <-db.writeAckC
}
+ // Continue, the write lock already acquired by previous writer
+ // and handed out to us.
case db.writeLockC <- struct{}{}:
case err = <-db.compPerErrC:
return
@@ -148,6 +167,7 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
danglingMerge := false
defer func() {
if danglingMerge {
+ // Only one dangling merge at most, so this is safe.
db.writeMergedC <- false
} else {
<-db.writeLockC
@@ -157,18 +177,18 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
}
}()
- mem, memFree, err := db.flush(b.size())
+ mdb, mdbFree, err := db.flush(b.size())
if err != nil {
return
}
- defer mem.decref()
+ defer mdb.decref()
// Calculate maximum size of the batch.
m := 1 << 20
if x := b.size(); x <= 128<<10 {
m = x + (128 << 10)
}
- m = minInt(m, memFree)
+ m = minInt(m, mdbFree)
// Merge with other batch.
drain:
@@ -197,7 +217,7 @@ drain:
select {
case db.journalC <- b:
// Write into memdb
- if berr := b.memReplay(mem.mdb); berr != nil {
+ if berr := b.memReplay(mdb.DB); berr != nil {
panic(berr)
}
case err = <-db.compPerErrC:
@@ -211,7 +231,7 @@ drain:
case err = <-db.journalAckC:
if err != nil {
// Revert memdb if error detected
- if berr := b.revertMemReplay(mem.mdb); berr != nil {
+ if berr := b.revertMemReplay(mdb.DB); berr != nil {
panic(berr)
}
return
@@ -225,7 +245,7 @@ drain:
if err != nil {
return
}
- if berr := b.memReplay(mem.mdb); berr != nil {
+ if berr := b.memReplay(mdb.DB); berr != nil {
panic(berr)
}
}
@@ -233,8 +253,8 @@ drain:
// Set last seq number.
db.addSeq(uint64(b.Len()))
- if b.size() >= memFree {
- db.rotateMem(0)
+ if b.size() >= mdbFree {
+ db.rotateMem(0, false)
}
return
}
@@ -249,8 +269,7 @@ func (db *DB) Put(key, value []byte, wo *opt.WriteOptions) error {
return db.Write(b, wo)
}
-// Delete deletes the value for the given key. It returns ErrNotFound if
-// the DB does not contain the key.
+// Delete deletes the value for the given key.
//
// It is safe to modify the contents of the arguments after Delete returns.
func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error {
@@ -290,16 +309,16 @@ func (db *DB) CompactRange(r util.Range) error {
}
// Check for overlaps in memdb.
- mem := db.getEffectiveMem()
- defer mem.decref()
- if isMemOverlaps(db.s.icmp, mem.mdb, r.Start, r.Limit) {
+ mdb := db.getEffectiveMem()
+ defer mdb.decref()
+ if isMemOverlaps(db.s.icmp, mdb.DB, r.Start, r.Limit) {
// Memdb compaction.
- if _, err := db.rotateMem(0); err != nil {
+ if _, err := db.rotateMem(0, false); err != nil {
<-db.writeLockC
return err
}
<-db.writeLockC
- if err := db.compSendIdle(db.mcompCmdC); err != nil {
+ if err := db.compTriggerWait(db.mcompCmdC); err != nil {
return err
}
} else {
@@ -307,5 +326,33 @@ func (db *DB) CompactRange(r util.Range) error {
}
// Table compaction.
- return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit)
+ return db.compTriggerRange(db.tcompCmdC, -1, r.Start, r.Limit)
+}
+
+// SetReadOnly makes DB read-only. It will stay read-only until reopened.
+func (db *DB) SetReadOnly() error {
+ if err := db.ok(); err != nil {
+ return err
+ }
+
+ // Lock writer.
+ select {
+ case db.writeLockC <- struct{}{}:
+ db.compWriteLocking = true
+ case err := <-db.compPerErrC:
+ return err
+ case _, _ = <-db.closeC:
+ return ErrClosed
+ }
+
+ // Set compaction read-only.
+ select {
+ case db.compErrSetC <- ErrReadOnly:
+ case perr := <-db.compPerErrC:
+ return perr
+ case _, _ = <-db.closeC:
+ return ErrClosed
+ }
+
+ return nil
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors.go
index 29d0d2f27..c8bd66a5a 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors.go
@@ -12,6 +12,7 @@ import (
var (
ErrNotFound = errors.ErrNotFound
+ ErrReadOnly = errors.New("leveldb: read-only mode")
ErrSnapshotReleased = errors.New("leveldb: snapshot released")
ErrIterReleased = errors.New("leveldb: iterator released")
ErrClosed = errors.New("leveldb: closed")
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go
index 84b5d6b7b..9a0f6e2c1 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/errors/errors.go
@@ -29,21 +29,21 @@ func New(text string) error {
// ErrCorrupted is the type that wraps errors that indicate corruption in
// the database.
type ErrCorrupted struct {
- File *storage.FileInfo
- Err error
+ Fd storage.FileDesc
+ Err error
}
func (e *ErrCorrupted) Error() string {
- if e.File != nil {
- return fmt.Sprintf("%v [file=%v]", e.Err, e.File)
+ if !e.Fd.Nil() {
+ return fmt.Sprintf("%v [file=%v]", e.Err, e.Fd)
} else {
return e.Err.Error()
}
}
// NewErrCorrupted creates new ErrCorrupted error.
-func NewErrCorrupted(f storage.File, err error) error {
- return &ErrCorrupted{storage.NewFileInfo(f), err}
+func NewErrCorrupted(fd storage.FileDesc, err error) error {
+ return &ErrCorrupted{fd, err}
}
// IsCorrupted returns a boolean indicating whether the error is indicating
@@ -52,24 +52,26 @@ func IsCorrupted(err error) bool {
switch err.(type) {
case *ErrCorrupted:
return true
+ case *storage.ErrCorrupted:
+ return true
}
return false
}
// ErrMissingFiles is the type that indicating a corruption due to missing
-// files.
+// files. ErrMissingFiles always wrapped with ErrCorrupted.
type ErrMissingFiles struct {
- Files []*storage.FileInfo
+ Fds []storage.FileDesc
}
func (e *ErrMissingFiles) Error() string { return "file missing" }
-// SetFile sets 'file info' of the given error with the given file.
+// SetFd sets 'file info' of the given error with the given file.
// Currently only ErrCorrupted is supported, otherwise will do nothing.
-func SetFile(err error, f storage.File) error {
+func SetFd(err error, fd storage.FileDesc) error {
switch x := err.(type) {
case *ErrCorrupted:
- x.File = storage.NewFileInfo(f)
+ x.Fd = fd
return x
}
return err
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/external_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/external_test.go
deleted file mode 100644
index b328ece4e..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/external_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
-
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-var _ = testutil.Defer(func() {
- Describe("Leveldb external", func() {
- o := &opt.Options{
- DisableBlockCache: true,
- BlockRestartInterval: 5,
- BlockSize: 80,
- Compression: opt.NoCompression,
- OpenFilesCacheCapacity: -1,
- Strict: opt.StrictAll,
- WriteBuffer: 1000,
- CompactionTableSize: 2000,
- }
-
- Describe("write test", func() {
- It("should do write correctly", func(done Done) {
- db := newTestingDB(o, nil, nil)
- t := testutil.DBTesting{
- DB: db,
- Deleted: testutil.KeyValue_Generate(nil, 500, 1, 50, 5, 5).Clone(),
- }
- testutil.DoDBTesting(&t)
- db.TestClose()
- done <- true
- }, 20.0)
- })
-
- Describe("read test", func() {
- testutil.AllKeyValueTesting(nil, nil, func(kv testutil.KeyValue) testutil.DB {
- // Building the DB.
- db := newTestingDB(o, nil, nil)
- kv.IterateShuffled(nil, func(i int, key, value []byte) {
- err := db.TestPut(key, value)
- Expect(err).NotTo(HaveOccurred())
- })
-
- return db
- }, func(db testutil.DB) {
- db.(*testingDB).TestClose()
- })
- })
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/filter/bloom_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/filter/bloom_test.go
deleted file mode 100644
index 1fb56f071..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/filter/bloom_test.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package filter
-
-import (
- "encoding/binary"
- "github.com/syndtr/goleveldb/leveldb/util"
- "testing"
-)
-
-type harness struct {
- t *testing.T
-
- bloom Filter
- generator FilterGenerator
- filter []byte
-}
-
-func newHarness(t *testing.T) *harness {
- bloom := NewBloomFilter(10)
- return &harness{
- t: t,
- bloom: bloom,
- generator: bloom.NewGenerator(),
- }
-}
-
-func (h *harness) add(key []byte) {
- h.generator.Add(key)
-}
-
-func (h *harness) addNum(key uint32) {
- var b [4]byte
- binary.LittleEndian.PutUint32(b[:], key)
- h.add(b[:])
-}
-
-func (h *harness) build() {
- b := &util.Buffer{}
- h.generator.Generate(b)
- h.filter = b.Bytes()
-}
-
-func (h *harness) reset() {
- h.filter = nil
-}
-
-func (h *harness) filterLen() int {
- return len(h.filter)
-}
-
-func (h *harness) assert(key []byte, want, silent bool) bool {
- got := h.bloom.Contains(h.filter, key)
- if !silent && got != want {
- h.t.Errorf("assert on '%v' failed got '%v', want '%v'", key, got, want)
- }
- return got
-}
-
-func (h *harness) assertNum(key uint32, want, silent bool) bool {
- var b [4]byte
- binary.LittleEndian.PutUint32(b[:], key)
- return h.assert(b[:], want, silent)
-}
-
-func TestBloomFilter_Empty(t *testing.T) {
- h := newHarness(t)
- h.build()
- h.assert([]byte("hello"), false, false)
- h.assert([]byte("world"), false, false)
-}
-
-func TestBloomFilter_Small(t *testing.T) {
- h := newHarness(t)
- h.add([]byte("hello"))
- h.add([]byte("world"))
- h.build()
- h.assert([]byte("hello"), true, false)
- h.assert([]byte("world"), true, false)
- h.assert([]byte("x"), false, false)
- h.assert([]byte("foo"), false, false)
-}
-
-func nextN(n int) int {
- switch {
- case n < 10:
- n += 1
- case n < 100:
- n += 10
- case n < 1000:
- n += 100
- default:
- n += 1000
- }
- return n
-}
-
-func TestBloomFilter_VaryingLengths(t *testing.T) {
- h := newHarness(t)
- var mediocre, good int
- for n := 1; n < 10000; n = nextN(n) {
- h.reset()
- for i := 0; i < n; i++ {
- h.addNum(uint32(i))
- }
- h.build()
-
- got := h.filterLen()
- want := (n * 10 / 8) + 40
- if got > want {
- t.Errorf("filter len test failed, '%d' > '%d'", got, want)
- }
-
- for i := 0; i < n; i++ {
- h.assertNum(uint32(i), true, false)
- }
-
- var rate float32
- for i := 0; i < 10000; i++ {
- if h.assertNum(uint32(i+1000000000), true, true) {
- rate++
- }
- }
- rate /= 10000
- if rate > 0.02 {
- t.Errorf("false positive rate is more than 2%%, got %v, at len %d", rate, n)
- }
- if rate > 0.0125 {
- mediocre++
- } else {
- good++
- }
- }
- t.Logf("false positive rate: %d good, %d mediocre", good, mediocre)
- if mediocre > good/5 {
- t.Error("mediocre false positive rate is more than expected")
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter_test.go
deleted file mode 100644
index 1ed6d07cb..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/array_iter_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package iterator_test
-
-import (
- . "github.com/onsi/ginkgo"
-
- . "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-var _ = testutil.Defer(func() {
- Describe("Array iterator", func() {
- It("Should iterates and seeks correctly", func() {
- // Build key/value.
- kv := testutil.KeyValue_Generate(nil, 70, 1, 5, 3, 3)
-
- // Test the iterator.
- t := testutil.IteratorTesting{
- KeyValue: kv.Clone(),
- Iter: NewArrayIterator(kv),
- }
- testutil.DoIteratorTesting(&t)
- })
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go
deleted file mode 100644
index 72a797892..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/indexed_iter_test.go
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package iterator_test
-
-import (
- "sort"
-
- . "github.com/onsi/ginkgo"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
- . "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-type keyValue struct {
- key []byte
- testutil.KeyValue
-}
-
-type keyValueIndex []keyValue
-
-func (x keyValueIndex) Search(key []byte) int {
- return sort.Search(x.Len(), func(i int) bool {
- return comparer.DefaultComparer.Compare(x[i].key, key) >= 0
- })
-}
-
-func (x keyValueIndex) Len() int { return len(x) }
-func (x keyValueIndex) Index(i int) (key, value []byte) { return x[i].key, nil }
-func (x keyValueIndex) Get(i int) Iterator { return NewArrayIterator(x[i]) }
-
-var _ = testutil.Defer(func() {
- Describe("Indexed iterator", func() {
- Test := func(n ...int) func() {
- if len(n) == 0 {
- rnd := testutil.NewRand()
- n = make([]int, rnd.Intn(17)+3)
- for i := range n {
- n[i] = rnd.Intn(19) + 1
- }
- }
-
- return func() {
- It("Should iterates and seeks correctly", func(done Done) {
- // Build key/value.
- index := make(keyValueIndex, len(n))
- sum := 0
- for _, x := range n {
- sum += x
- }
- kv := testutil.KeyValue_Generate(nil, sum, 1, 10, 4, 4)
- for i, j := 0, 0; i < len(n); i++ {
- for x := n[i]; x > 0; x-- {
- key, value := kv.Index(j)
- index[i].key = key
- index[i].Put(key, value)
- j++
- }
- }
-
- // Test the iterator.
- t := testutil.IteratorTesting{
- KeyValue: kv.Clone(),
- Iter: NewIndexedIterator(NewArrayIndexer(index), true),
- }
- testutil.DoIteratorTesting(&t)
- done <- true
- }, 1.5)
- }
- }
-
- Describe("with 100 keys", Test(100))
- Describe("with 50-50 keys", Test(50, 50))
- Describe("with 50-1 keys", Test(50, 1))
- Describe("with 50-1-50 keys", Test(50, 1, 50))
- Describe("with 1-50 keys", Test(1, 50))
- Describe("with random N-keys", Test())
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go
deleted file mode 100644
index 5ef8d5baf..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/iter_suite_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package iterator_test
-
-import (
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-func TestIterator(t *testing.T) {
- testutil.RunSuite(t, "Iterator Suite")
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter_test.go
deleted file mode 100644
index e523b63e4..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/iterator/merged_iter_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package iterator_test
-
-import (
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
- . "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-var _ = testutil.Defer(func() {
- Describe("Merged iterator", func() {
- Test := func(filled int, empty int) func() {
- return func() {
- It("Should iterates and seeks correctly", func(done Done) {
- rnd := testutil.NewRand()
-
- // Build key/value.
- filledKV := make([]testutil.KeyValue, filled)
- kv := testutil.KeyValue_Generate(nil, 100, 1, 10, 4, 4)
- kv.Iterate(func(i int, key, value []byte) {
- filledKV[rnd.Intn(filled)].Put(key, value)
- })
-
- // Create itearators.
- iters := make([]Iterator, filled+empty)
- for i := range iters {
- if empty == 0 || (rnd.Int()%2 == 0 && filled > 0) {
- filled--
- Expect(filledKV[filled].Len()).ShouldNot(BeZero())
- iters[i] = NewArrayIterator(filledKV[filled])
- } else {
- empty--
- iters[i] = NewEmptyIterator(nil)
- }
- }
-
- // Test the iterator.
- t := testutil.IteratorTesting{
- KeyValue: kv.Clone(),
- Iter: NewMergedIterator(iters, comparer.DefaultComparer, true),
- }
- testutil.DoIteratorTesting(&t)
- done <- true
- }, 1.5)
- }
- }
-
- Describe("with three, all filled iterators", Test(3, 0))
- Describe("with one filled, one empty iterators", Test(1, 1))
- Describe("with one filled, two empty iterators", Test(1, 2))
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go
index 6519ec660..891098bb7 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal.go
@@ -83,6 +83,7 @@ import (
"io"
"github.com/syndtr/goleveldb/leveldb/errors"
+ "github.com/syndtr/goleveldb/leveldb/storage"
"github.com/syndtr/goleveldb/leveldb/util"
)
@@ -165,7 +166,7 @@ func (r *Reader) corrupt(n int, reason string, skip bool) error {
r.dropper.Drop(&ErrCorrupted{n, reason})
}
if r.strict && !skip {
- r.err = errors.NewErrCorrupted(nil, &ErrCorrupted{n, reason})
+ r.err = errors.NewErrCorrupted(storage.FileDesc{}, &ErrCorrupted{n, reason})
return r.err
}
return errSkip
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go
deleted file mode 100644
index 0fcf22599..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/journal/journal_test.go
+++ /dev/null
@@ -1,818 +0,0 @@
-// Copyright 2011 The LevelDB-Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Taken from: https://code.google.com/p/leveldb-go/source/browse/leveldb/record/record_test.go?r=df1fa28f7f3be6c3935548169002309c12967135
-// License, authors and contributors informations can be found at bellow URLs respectively:
-// https://code.google.com/p/leveldb-go/source/browse/LICENSE
-// https://code.google.com/p/leveldb-go/source/browse/AUTHORS
-// https://code.google.com/p/leveldb-go/source/browse/CONTRIBUTORS
-
-package journal
-
-import (
- "bytes"
- "encoding/binary"
- "fmt"
- "io"
- "io/ioutil"
- "math/rand"
- "strings"
- "testing"
-)
-
-type dropper struct {
- t *testing.T
-}
-
-func (d dropper) Drop(err error) {
- d.t.Log(err)
-}
-
-func short(s string) string {
- if len(s) < 64 {
- return s
- }
- return fmt.Sprintf("%s...(skipping %d bytes)...%s", s[:20], len(s)-40, s[len(s)-20:])
-}
-
-// big returns a string of length n, composed of repetitions of partial.
-func big(partial string, n int) string {
- return strings.Repeat(partial, n/len(partial)+1)[:n]
-}
-
-func TestEmpty(t *testing.T) {
- buf := new(bytes.Buffer)
- r := NewReader(buf, dropper{t}, true, true)
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("got %v, want %v", err, io.EOF)
- }
-}
-
-func testGenerator(t *testing.T, reset func(), gen func() (string, bool)) {
- buf := new(bytes.Buffer)
-
- reset()
- w := NewWriter(buf)
- for {
- s, ok := gen()
- if !ok {
- break
- }
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write([]byte(s)); err != nil {
- t.Fatal(err)
- }
- }
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- reset()
- r := NewReader(buf, dropper{t}, true, true)
- for {
- s, ok := gen()
- if !ok {
- break
- }
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- x, err := ioutil.ReadAll(rr)
- if err != nil {
- t.Fatal(err)
- }
- if string(x) != s {
- t.Fatalf("got %q, want %q", short(string(x)), short(s))
- }
- }
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("got %v, want %v", err, io.EOF)
- }
-}
-
-func testLiterals(t *testing.T, s []string) {
- var i int
- reset := func() {
- i = 0
- }
- gen := func() (string, bool) {
- if i == len(s) {
- return "", false
- }
- i++
- return s[i-1], true
- }
- testGenerator(t, reset, gen)
-}
-
-func TestMany(t *testing.T) {
- const n = 1e5
- var i int
- reset := func() {
- i = 0
- }
- gen := func() (string, bool) {
- if i == n {
- return "", false
- }
- i++
- return fmt.Sprintf("%d.", i-1), true
- }
- testGenerator(t, reset, gen)
-}
-
-func TestRandom(t *testing.T) {
- const n = 1e2
- var (
- i int
- r *rand.Rand
- )
- reset := func() {
- i, r = 0, rand.New(rand.NewSource(0))
- }
- gen := func() (string, bool) {
- if i == n {
- return "", false
- }
- i++
- return strings.Repeat(string(uint8(i)), r.Intn(2*blockSize+16)), true
- }
- testGenerator(t, reset, gen)
-}
-
-func TestBasic(t *testing.T) {
- testLiterals(t, []string{
- strings.Repeat("a", 1000),
- strings.Repeat("b", 97270),
- strings.Repeat("c", 8000),
- })
-}
-
-func TestBoundary(t *testing.T) {
- for i := blockSize - 16; i < blockSize+16; i++ {
- s0 := big("abcd", i)
- for j := blockSize - 16; j < blockSize+16; j++ {
- s1 := big("ABCDE", j)
- testLiterals(t, []string{s0, s1})
- testLiterals(t, []string{s0, "", s1})
- testLiterals(t, []string{s0, "x", s1})
- }
- }
-}
-
-func TestFlush(t *testing.T) {
- buf := new(bytes.Buffer)
- w := NewWriter(buf)
- // Write a couple of records. Everything should still be held
- // in the record.Writer buffer, so that buf.Len should be 0.
- w0, _ := w.Next()
- w0.Write([]byte("0"))
- w1, _ := w.Next()
- w1.Write([]byte("11"))
- if got, want := buf.Len(), 0; got != want {
- t.Fatalf("buffer length #0: got %d want %d", got, want)
- }
- // Flush the record.Writer buffer, which should yield 17 bytes.
- // 17 = 2*7 + 1 + 2, which is two headers and 1 + 2 payload bytes.
- if err := w.Flush(); err != nil {
- t.Fatal(err)
- }
- if got, want := buf.Len(), 17; got != want {
- t.Fatalf("buffer length #1: got %d want %d", got, want)
- }
- // Do another write, one that isn't large enough to complete the block.
- // The write should not have flowed through to buf.
- w2, _ := w.Next()
- w2.Write(bytes.Repeat([]byte("2"), 10000))
- if got, want := buf.Len(), 17; got != want {
- t.Fatalf("buffer length #2: got %d want %d", got, want)
- }
- // Flushing should get us up to 10024 bytes written.
- // 10024 = 17 + 7 + 10000.
- if err := w.Flush(); err != nil {
- t.Fatal(err)
- }
- if got, want := buf.Len(), 10024; got != want {
- t.Fatalf("buffer length #3: got %d want %d", got, want)
- }
- // Do a bigger write, one that completes the current block.
- // We should now have 32768 bytes (a complete block), without
- // an explicit flush.
- w3, _ := w.Next()
- w3.Write(bytes.Repeat([]byte("3"), 40000))
- if got, want := buf.Len(), 32768; got != want {
- t.Fatalf("buffer length #4: got %d want %d", got, want)
- }
- // Flushing should get us up to 50038 bytes written.
- // 50038 = 10024 + 2*7 + 40000. There are two headers because
- // the one record was split into two chunks.
- if err := w.Flush(); err != nil {
- t.Fatal(err)
- }
- if got, want := buf.Len(), 50038; got != want {
- t.Fatalf("buffer length #5: got %d want %d", got, want)
- }
- // Check that reading those records give the right lengths.
- r := NewReader(buf, dropper{t}, true, true)
- wants := []int64{1, 2, 10000, 40000}
- for i, want := range wants {
- rr, _ := r.Next()
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #%d: %v", i, err)
- }
- if n != want {
- t.Fatalf("read #%d: got %d bytes want %d", i, n, want)
- }
- }
-}
-
-func TestNonExhaustiveRead(t *testing.T) {
- const n = 100
- buf := new(bytes.Buffer)
- p := make([]byte, 10)
- rnd := rand.New(rand.NewSource(1))
-
- w := NewWriter(buf)
- for i := 0; i < n; i++ {
- length := len(p) + rnd.Intn(3*blockSize)
- s := string(uint8(i)) + "123456789abcdefgh"
- ww, _ := w.Next()
- ww.Write([]byte(big(s, length)))
- }
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- r := NewReader(buf, dropper{t}, true, true)
- for i := 0; i < n; i++ {
- rr, _ := r.Next()
- _, err := io.ReadFull(rr, p)
- if err != nil {
- t.Fatal(err)
- }
- want := string(uint8(i)) + "123456789"
- if got := string(p); got != want {
- t.Fatalf("read #%d: got %q want %q", i, got, want)
- }
- }
-}
-
-func TestStaleReader(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
- w0, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- w0.Write([]byte("0"))
- w1, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- w1.Write([]byte("11"))
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- r := NewReader(buf, dropper{t}, true, true)
- r0, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- r1, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- p := make([]byte, 1)
- if _, err := r0.Read(p); err == nil || !strings.Contains(err.Error(), "stale") {
- t.Fatalf("stale read #0: unexpected error: %v", err)
- }
- if _, err := r1.Read(p); err != nil {
- t.Fatalf("fresh read #1: got %v want nil error", err)
- }
- if p[0] != '1' {
- t.Fatalf("fresh read #1: byte contents: got '%c' want '1'", p[0])
- }
-}
-
-func TestStaleWriter(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
- w0, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- w1, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := w0.Write([]byte("0")); err == nil || !strings.Contains(err.Error(), "stale") {
- t.Fatalf("stale write #0: unexpected error: %v", err)
- }
- if _, err := w1.Write([]byte("11")); err != nil {
- t.Fatalf("fresh write #1: got %v want nil error", err)
- }
- if err := w.Flush(); err != nil {
- t.Fatalf("flush: %v", err)
- }
- if _, err := w1.Write([]byte("0")); err == nil || !strings.Contains(err.Error(), "stale") {
- t.Fatalf("stale write #1: unexpected error: %v", err)
- }
-}
-
-func TestCorrupt_MissingLastBlock(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
-
- // First record.
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-1024)); err != nil {
- t.Fatalf("write #0: unexpected error: %v", err)
- }
-
- // Second record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
- t.Fatalf("write #1: unexpected error: %v", err)
- }
-
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- // Cut the last block.
- b := buf.Bytes()[:blockSize]
- r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
-
- // First read.
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #0: %v", err)
- }
- if n != blockSize-1024 {
- t.Fatalf("read #0: got %d bytes want %d", n, blockSize-1024)
- }
-
- // Second read.
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != io.ErrUnexpectedEOF {
- t.Fatalf("read #1: unexpected error: %v", err)
- }
-
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("last next: unexpected error: %v", err)
- }
-}
-
-func TestCorrupt_CorruptedFirstBlock(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
-
- // First record.
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
- t.Fatalf("write #0: unexpected error: %v", err)
- }
-
- // Second record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
- t.Fatalf("write #1: unexpected error: %v", err)
- }
-
- // Third record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
- t.Fatalf("write #2: unexpected error: %v", err)
- }
-
- // Fourth record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil {
- t.Fatalf("write #3: unexpected error: %v", err)
- }
-
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- b := buf.Bytes()
- // Corrupting block #0.
- for i := 0; i < 1024; i++ {
- b[i] = '1'
- }
-
- r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
-
- // First read (third record).
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #0: %v", err)
- }
- if want := int64(blockSize-headerSize) + 1; n != want {
- t.Fatalf("read #0: got %d bytes want %d", n, want)
- }
-
- // Second read (fourth record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #1: %v", err)
- }
- if want := int64(blockSize-headerSize) + 2; n != want {
- t.Fatalf("read #1: got %d bytes want %d", n, want)
- }
-
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("last next: unexpected error: %v", err)
- }
-}
-
-func TestCorrupt_CorruptedMiddleBlock(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
-
- // First record.
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
- t.Fatalf("write #0: unexpected error: %v", err)
- }
-
- // Second record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
- t.Fatalf("write #1: unexpected error: %v", err)
- }
-
- // Third record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
- t.Fatalf("write #2: unexpected error: %v", err)
- }
-
- // Fourth record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil {
- t.Fatalf("write #3: unexpected error: %v", err)
- }
-
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- b := buf.Bytes()
- // Corrupting block #1.
- for i := 0; i < 1024; i++ {
- b[blockSize+i] = '1'
- }
-
- r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
-
- // First read (first record).
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #0: %v", err)
- }
- if want := int64(blockSize / 2); n != want {
- t.Fatalf("read #0: got %d bytes want %d", n, want)
- }
-
- // Second read (second record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != io.ErrUnexpectedEOF {
- t.Fatalf("read #1: unexpected error: %v", err)
- }
-
- // Third read (fourth record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #2: %v", err)
- }
- if want := int64(blockSize-headerSize) + 2; n != want {
- t.Fatalf("read #2: got %d bytes want %d", n, want)
- }
-
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("last next: unexpected error: %v", err)
- }
-}
-
-func TestCorrupt_CorruptedLastBlock(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
-
- // First record.
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
- t.Fatalf("write #0: unexpected error: %v", err)
- }
-
- // Second record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
- t.Fatalf("write #1: unexpected error: %v", err)
- }
-
- // Third record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
- t.Fatalf("write #2: unexpected error: %v", err)
- }
-
- // Fourth record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+2)); err != nil {
- t.Fatalf("write #3: unexpected error: %v", err)
- }
-
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- b := buf.Bytes()
- // Corrupting block #3.
- for i := len(b) - 1; i > len(b)-1024; i-- {
- b[i] = '1'
- }
-
- r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
-
- // First read (first record).
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #0: %v", err)
- }
- if want := int64(blockSize / 2); n != want {
- t.Fatalf("read #0: got %d bytes want %d", n, want)
- }
-
- // Second read (second record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #1: %v", err)
- }
- if want := int64(blockSize - headerSize); n != want {
- t.Fatalf("read #1: got %d bytes want %d", n, want)
- }
-
- // Third read (third record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #2: %v", err)
- }
- if want := int64(blockSize-headerSize) + 1; n != want {
- t.Fatalf("read #2: got %d bytes want %d", n, want)
- }
-
- // Fourth read (fourth record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != io.ErrUnexpectedEOF {
- t.Fatalf("read #3: unexpected error: %v", err)
- }
-
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("last next: unexpected error: %v", err)
- }
-}
-
-func TestCorrupt_FirstChuckLengthOverflow(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
-
- // First record.
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
- t.Fatalf("write #0: unexpected error: %v", err)
- }
-
- // Second record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
- t.Fatalf("write #1: unexpected error: %v", err)
- }
-
- // Third record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
- t.Fatalf("write #2: unexpected error: %v", err)
- }
-
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- b := buf.Bytes()
- // Corrupting record #1.
- x := blockSize
- binary.LittleEndian.PutUint16(b[x+4:], 0xffff)
-
- r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
-
- // First read (first record).
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #0: %v", err)
- }
- if want := int64(blockSize / 2); n != want {
- t.Fatalf("read #0: got %d bytes want %d", n, want)
- }
-
- // Second read (second record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != io.ErrUnexpectedEOF {
- t.Fatalf("read #1: unexpected error: %v", err)
- }
-
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("last next: unexpected error: %v", err)
- }
-}
-
-func TestCorrupt_MiddleChuckLengthOverflow(t *testing.T) {
- buf := new(bytes.Buffer)
-
- w := NewWriter(buf)
-
- // First record.
- ww, err := w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize/2)); err != nil {
- t.Fatalf("write #0: unexpected error: %v", err)
- }
-
- // Second record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), blockSize-headerSize)); err != nil {
- t.Fatalf("write #1: unexpected error: %v", err)
- }
-
- // Third record.
- ww, err = w.Next()
- if err != nil {
- t.Fatal(err)
- }
- if _, err := ww.Write(bytes.Repeat([]byte("0"), (blockSize-headerSize)+1)); err != nil {
- t.Fatalf("write #2: unexpected error: %v", err)
- }
-
- if err := w.Close(); err != nil {
- t.Fatal(err)
- }
-
- b := buf.Bytes()
- // Corrupting record #1.
- x := blockSize/2 + headerSize
- binary.LittleEndian.PutUint16(b[x+4:], 0xffff)
-
- r := NewReader(bytes.NewReader(b), dropper{t}, false, true)
-
- // First read (first record).
- rr, err := r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err := io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #0: %v", err)
- }
- if want := int64(blockSize / 2); n != want {
- t.Fatalf("read #0: got %d bytes want %d", n, want)
- }
-
- // Second read (third record).
- rr, err = r.Next()
- if err != nil {
- t.Fatal(err)
- }
- n, err = io.Copy(ioutil.Discard, rr)
- if err != nil {
- t.Fatalf("read #1: %v", err)
- }
- if want := int64(blockSize-headerSize) + 1; n != want {
- t.Fatalf("read #1: got %d bytes want %d", n, want)
- }
-
- if _, err := r.Next(); err != io.EOF {
- t.Fatalf("last next: unexpected error: %v", err)
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key.go
index 572ae8150..1443c7526 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key.go
@@ -11,6 +11,7 @@ import (
"fmt"
"github.com/syndtr/goleveldb/leveldb/errors"
+ "github.com/syndtr/goleveldb/leveldb/storage"
)
type ErrIkeyCorrupted struct {
@@ -23,7 +24,7 @@ func (e *ErrIkeyCorrupted) Error() string {
}
func newErrIkeyCorrupted(ikey []byte, reason string) error {
- return errors.NewErrCorrupted(nil, &ErrIkeyCorrupted{append([]byte{}, ikey...), reason})
+ return errors.NewErrCorrupted(storage.FileDesc{}, &ErrIkeyCorrupted{append([]byte{}, ikey...), reason})
}
type kType int
@@ -70,17 +71,21 @@ func init() {
type iKey []byte
-func newIkey(ukey []byte, seq uint64, kt kType) iKey {
+func makeIkey(dst, ukey []byte, seq uint64, kt kType) iKey {
if seq > kMaxSeq {
panic("leveldb: invalid sequence number")
} else if kt > ktVal {
panic("leveldb: invalid type")
}
- ik := make(iKey, len(ukey)+8)
- copy(ik, ukey)
- binary.LittleEndian.PutUint64(ik[len(ukey):], (seq<<8)|uint64(kt))
- return ik
+ if n := len(ukey) + 8; cap(dst) < n {
+ dst = make([]byte, n)
+ } else {
+ dst = dst[:n]
+ }
+ copy(dst, ukey)
+ binary.LittleEndian.PutUint64(dst[len(ukey):], (seq<<8)|uint64(kt))
+ return iKey(dst)
}
func parseIkey(ik []byte) (ukey []byte, seq uint64, kt kType, err error) {
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key_test.go
deleted file mode 100644
index 30eadf784..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/key_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- "bytes"
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
-)
-
-var defaultIComparer = &iComparer{comparer.DefaultComparer}
-
-func ikey(key string, seq uint64, kt kType) iKey {
- return newIkey([]byte(key), uint64(seq), kt)
-}
-
-func shortSep(a, b []byte) []byte {
- dst := make([]byte, len(a))
- dst = defaultIComparer.Separator(dst[:0], a, b)
- if dst == nil {
- return a
- }
- return dst
-}
-
-func shortSuccessor(b []byte) []byte {
- dst := make([]byte, len(b))
- dst = defaultIComparer.Successor(dst[:0], b)
- if dst == nil {
- return b
- }
- return dst
-}
-
-func testSingleKey(t *testing.T, key string, seq uint64, kt kType) {
- ik := ikey(key, seq, kt)
-
- if !bytes.Equal(ik.ukey(), []byte(key)) {
- t.Errorf("user key does not equal, got %v, want %v", string(ik.ukey()), key)
- }
-
- rseq, rt := ik.parseNum()
- if rseq != seq {
- t.Errorf("seq number does not equal, got %v, want %v", rseq, seq)
- }
- if rt != kt {
- t.Errorf("type does not equal, got %v, want %v", rt, kt)
- }
-
- if rukey, rseq, rt, kerr := parseIkey(ik); kerr == nil {
- if !bytes.Equal(rukey, []byte(key)) {
- t.Errorf("user key does not equal, got %v, want %v", string(ik.ukey()), key)
- }
- if rseq != seq {
- t.Errorf("seq number does not equal, got %v, want %v", rseq, seq)
- }
- if rt != kt {
- t.Errorf("type does not equal, got %v, want %v", rt, kt)
- }
- } else {
- t.Errorf("key error: %v", kerr)
- }
-}
-
-func TestIkey_EncodeDecode(t *testing.T) {
- keys := []string{"", "k", "hello", "longggggggggggggggggggggg"}
- seqs := []uint64{
- 1, 2, 3,
- (1 << 8) - 1, 1 << 8, (1 << 8) + 1,
- (1 << 16) - 1, 1 << 16, (1 << 16) + 1,
- (1 << 32) - 1, 1 << 32, (1 << 32) + 1,
- }
- for _, key := range keys {
- for _, seq := range seqs {
- testSingleKey(t, key, seq, ktVal)
- testSingleKey(t, "hello", 1, ktDel)
- }
- }
-}
-
-func assertBytes(t *testing.T, want, got []byte) {
- if !bytes.Equal(got, want) {
- t.Errorf("assert failed, got %v, want %v", got, want)
- }
-}
-
-func TestIkeyShortSeparator(t *testing.T) {
- // When user keys are same
- assertBytes(t, ikey("foo", 100, ktVal),
- shortSep(ikey("foo", 100, ktVal),
- ikey("foo", 99, ktVal)))
- assertBytes(t, ikey("foo", 100, ktVal),
- shortSep(ikey("foo", 100, ktVal),
- ikey("foo", 101, ktVal)))
- assertBytes(t, ikey("foo", 100, ktVal),
- shortSep(ikey("foo", 100, ktVal),
- ikey("foo", 100, ktVal)))
- assertBytes(t, ikey("foo", 100, ktVal),
- shortSep(ikey("foo", 100, ktVal),
- ikey("foo", 100, ktDel)))
-
- // When user keys are misordered
- assertBytes(t, ikey("foo", 100, ktVal),
- shortSep(ikey("foo", 100, ktVal),
- ikey("bar", 99, ktVal)))
-
- // When user keys are different, but correctly ordered
- assertBytes(t, ikey("g", uint64(kMaxSeq), ktSeek),
- shortSep(ikey("foo", 100, ktVal),
- ikey("hello", 200, ktVal)))
-
- // When start user key is prefix of limit user key
- assertBytes(t, ikey("foo", 100, ktVal),
- shortSep(ikey("foo", 100, ktVal),
- ikey("foobar", 200, ktVal)))
-
- // When limit user key is prefix of start user key
- assertBytes(t, ikey("foobar", 100, ktVal),
- shortSep(ikey("foobar", 100, ktVal),
- ikey("foo", 200, ktVal)))
-}
-
-func TestIkeyShortestSuccessor(t *testing.T) {
- assertBytes(t, ikey("g", uint64(kMaxSeq), ktSeek),
- shortSuccessor(ikey("foo", 100, ktVal)))
- assertBytes(t, ikey("\xff\xff", 100, ktVal),
- shortSuccessor(ikey("\xff\xff", 100, ktVal)))
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go
deleted file mode 100644
index fefa007a7..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/leveldb_suite_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package leveldb
-
-import (
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-func TestLevelDB(t *testing.T) {
- testutil.RunSuite(t, "LevelDB Suite")
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/bench_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/bench_test.go
deleted file mode 100644
index b05084caa..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/bench_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package memdb
-
-import (
- "encoding/binary"
- "math/rand"
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
-)
-
-func BenchmarkPut(b *testing.B) {
- buf := make([][4]byte, b.N)
- for i := range buf {
- binary.LittleEndian.PutUint32(buf[i][:], uint32(i))
- }
-
- b.ResetTimer()
- p := New(comparer.DefaultComparer, 0)
- for i := range buf {
- p.Put(buf[i][:], nil)
- }
-}
-
-func BenchmarkPutRandom(b *testing.B) {
- buf := make([][4]byte, b.N)
- for i := range buf {
- binary.LittleEndian.PutUint32(buf[i][:], uint32(rand.Int()))
- }
-
- b.ResetTimer()
- p := New(comparer.DefaultComparer, 0)
- for i := range buf {
- p.Put(buf[i][:], nil)
- }
-}
-
-func BenchmarkGet(b *testing.B) {
- buf := make([][4]byte, b.N)
- for i := range buf {
- binary.LittleEndian.PutUint32(buf[i][:], uint32(i))
- }
-
- p := New(comparer.DefaultComparer, 0)
- for i := range buf {
- p.Put(buf[i][:], nil)
- }
-
- b.ResetTimer()
- for i := range buf {
- p.Get(buf[i][:])
- }
-}
-
-func BenchmarkGetRandom(b *testing.B) {
- buf := make([][4]byte, b.N)
- for i := range buf {
- binary.LittleEndian.PutUint32(buf[i][:], uint32(i))
- }
-
- p := New(comparer.DefaultComparer, 0)
- for i := range buf {
- p.Put(buf[i][:], nil)
- }
-
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- p.Get(buf[rand.Int()%b.N][:])
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go
index e5398873b..1395bd928 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb.go
@@ -206,6 +206,7 @@ func (p *DB) randHeight() (h int) {
return
}
+// Must hold RW-lock if prev == true, as it use shared prevNode slice.
func (p *DB) findGE(key []byte, prev bool) (int, bool) {
node := 0
h := p.maxHeight - 1
@@ -302,7 +303,7 @@ func (p *DB) Put(key []byte, value []byte) error {
node := len(p.nodeData)
p.nodeData = append(p.nodeData, kvOffset, len(key), len(value), h)
for i, n := range p.prevNode[:h] {
- m := n + 4 + i
+ m := n + nNext + i
p.nodeData = append(p.nodeData, p.nodeData[m])
p.nodeData[m] = node
}
@@ -434,20 +435,22 @@ func (p *DB) Len() int {
// Reset resets the DB to initial empty state. Allows reuse the buffer.
func (p *DB) Reset() {
+ p.mu.Lock()
p.rnd = rand.New(rand.NewSource(0xdeadbeef))
p.maxHeight = 1
p.n = 0
p.kvSize = 0
p.kvData = p.kvData[:0]
- p.nodeData = p.nodeData[:4+tMaxHeight]
+ p.nodeData = p.nodeData[:nNext+tMaxHeight]
p.nodeData[nKV] = 0
p.nodeData[nKey] = 0
p.nodeData[nVal] = 0
p.nodeData[nHeight] = tMaxHeight
for n := 0; n < tMaxHeight; n++ {
- p.nodeData[4+n] = 0
+ p.nodeData[nNext+n] = 0
p.prevNode[n] = 0
}
+ p.mu.Unlock()
}
// New creates a new initalized in-memory key/value DB. The capacity
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go
deleted file mode 100644
index 18c304b7f..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_suite_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package memdb
-
-import (
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-func TestMemDB(t *testing.T) {
- testutil.RunSuite(t, "MemDB Suite")
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go
deleted file mode 100644
index 5dd6dbc7b..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/memdb/memdb_test.go
+++ /dev/null
@@ -1,135 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package memdb
-
-import (
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/testutil"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-func (p *DB) TestFindLT(key []byte) (rkey, value []byte, err error) {
- p.mu.RLock()
- if node := p.findLT(key); node != 0 {
- n := p.nodeData[node]
- m := n + p.nodeData[node+nKey]
- rkey = p.kvData[n:m]
- value = p.kvData[m : m+p.nodeData[node+nVal]]
- } else {
- err = ErrNotFound
- }
- p.mu.RUnlock()
- return
-}
-
-func (p *DB) TestFindLast() (rkey, value []byte, err error) {
- p.mu.RLock()
- if node := p.findLast(); node != 0 {
- n := p.nodeData[node]
- m := n + p.nodeData[node+nKey]
- rkey = p.kvData[n:m]
- value = p.kvData[m : m+p.nodeData[node+nVal]]
- } else {
- err = ErrNotFound
- }
- p.mu.RUnlock()
- return
-}
-
-func (p *DB) TestPut(key []byte, value []byte) error {
- p.Put(key, value)
- return nil
-}
-
-func (p *DB) TestDelete(key []byte) error {
- p.Delete(key)
- return nil
-}
-
-func (p *DB) TestFind(key []byte) (rkey, rvalue []byte, err error) {
- return p.Find(key)
-}
-
-func (p *DB) TestGet(key []byte) (value []byte, err error) {
- return p.Get(key)
-}
-
-func (p *DB) TestNewIterator(slice *util.Range) iterator.Iterator {
- return p.NewIterator(slice)
-}
-
-var _ = testutil.Defer(func() {
- Describe("Memdb", func() {
- Describe("write test", func() {
- It("should do write correctly", func() {
- db := New(comparer.DefaultComparer, 0)
- t := testutil.DBTesting{
- DB: db,
- Deleted: testutil.KeyValue_Generate(nil, 1000, 1, 30, 5, 5).Clone(),
- PostFn: func(t *testutil.DBTesting) {
- Expect(db.Len()).Should(Equal(t.Present.Len()))
- Expect(db.Size()).Should(Equal(t.Present.Size()))
- switch t.Act {
- case testutil.DBPut, testutil.DBOverwrite:
- Expect(db.Contains(t.ActKey)).Should(BeTrue())
- default:
- Expect(db.Contains(t.ActKey)).Should(BeFalse())
- }
- },
- }
- testutil.DoDBTesting(&t)
- })
- })
-
- Describe("read test", func() {
- testutil.AllKeyValueTesting(nil, func(kv testutil.KeyValue) testutil.DB {
- // Building the DB.
- db := New(comparer.DefaultComparer, 0)
- kv.IterateShuffled(nil, func(i int, key, value []byte) {
- db.Put(key, value)
- })
-
- if kv.Len() > 1 {
- It("Should find correct keys with findLT", func() {
- testutil.ShuffledIndex(nil, kv.Len()-1, 1, func(i int) {
- key_, key, _ := kv.IndexInexact(i + 1)
- expectedKey, expectedValue := kv.Index(i)
-
- // Using key that exist.
- rkey, rvalue, err := db.TestFindLT(key)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q -> %q", key, expectedKey)
- Expect(rkey).Should(Equal(expectedKey), "Key")
- Expect(rvalue).Should(Equal(expectedValue), "Value for key %q -> %q", key, expectedKey)
-
- // Using key that doesn't exist.
- rkey, rvalue, err = db.TestFindLT(key_)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q (%q) -> %q", key_, key, expectedKey)
- Expect(rkey).Should(Equal(expectedKey))
- Expect(rvalue).Should(Equal(expectedValue), "Value for key %q (%q) -> %q", key_, key, expectedKey)
- })
- })
- }
-
- if kv.Len() > 0 {
- It("Should find last key with findLast", func() {
- key, value := kv.Index(kv.Len() - 1)
- rkey, rvalue, err := db.TestFindLast()
- Expect(err).ShouldNot(HaveOccurred())
- Expect(rkey).Should(Equal(key))
- Expect(rvalue).Should(Equal(value))
- })
- }
-
- return db
- }, nil, nil)
- })
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go
index 61f0eadf9..3d2bf1c02 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go
@@ -8,10 +8,11 @@
package opt
import (
+ "math"
+
"github.com/syndtr/goleveldb/leveldb/cache"
"github.com/syndtr/goleveldb/leveldb/comparer"
"github.com/syndtr/goleveldb/leveldb/filter"
- "math"
)
const (
@@ -35,8 +36,6 @@ var (
DefaultCompactionTotalSizeMultiplier = 10.0
DefaultCompressionType = SnappyCompression
DefaultIteratorSamplingRate = 1 * MiB
- DefaultMaxMemCompationLevel = 2
- DefaultNumLevel = 7
DefaultOpenFilesCacher = LRUCacher
DefaultOpenFilesCacheCapacity = 500
DefaultWriteBuffer = 4 * MiB
@@ -250,6 +249,11 @@ type Options struct {
// The default value (DefaultCompression) uses snappy compression.
Compression Compression
+ // DisableBufferPool allows disable use of util.BufferPool functionality.
+ //
+ // The default value is false.
+ DisableBufferPool bool
+
// DisableBlockCache allows disable use of cache.Cache functionality on
// 'sorted table' block.
//
@@ -261,6 +265,13 @@ type Options struct {
// The default value is false.
DisableCompactionBackoff bool
+ // DisableLargeBatchTransaction allows disabling switch-to-transaction mode
+ // on large batch write. If enable batch writes large than WriteBuffer will
+ // use transaction.
+ //
+ // The default is false.
+ DisableLargeBatchTransaction bool
+
// ErrorIfExist defines whether an error should returned if the DB already
// exist.
//
@@ -296,18 +307,10 @@ type Options struct {
// The default is 1MiB.
IteratorSamplingRate int
- // MaxMemCompationLevel defines maximum level a newly compacted 'memdb'
- // will be pushed into if doesn't creates overlap. This should less than
- // NumLevel. Use -1 for level-0.
+ // NoSync allows completely disable fsync.
//
- // The default is 2.
- MaxMemCompationLevel int
-
- // NumLevel defines number of database level. The level shouldn't changed
- // between opens, or the database will panic.
- //
- // The default is 7.
- NumLevel int
+ // The default is false.
+ NoSync bool
// OpenFilesCacher provides cache algorithm for open files caching.
// Specify NoCacher to disable caching algorithm.
@@ -321,6 +324,11 @@ type Options struct {
// The default value is 500.
OpenFilesCacheCapacity int
+ // If true then opens DB in read-only mode.
+ //
+ // The default value is false.
+ ReadOnly bool
+
// Strict defines the DB strict level.
Strict Strict
@@ -425,7 +433,7 @@ func (o *Options) GetCompactionTableSize(level int) int {
if o.CompactionTableSize > 0 {
base = o.CompactionTableSize
}
- if len(o.CompactionTableSizeMultiplierPerLevel) > level && o.CompactionTableSizeMultiplierPerLevel[level] > 0 {
+ if level < len(o.CompactionTableSizeMultiplierPerLevel) && o.CompactionTableSizeMultiplierPerLevel[level] > 0 {
mult = o.CompactionTableSizeMultiplierPerLevel[level]
} else if o.CompactionTableSizeMultiplier > 0 {
mult = math.Pow(o.CompactionTableSizeMultiplier, float64(level))
@@ -446,7 +454,7 @@ func (o *Options) GetCompactionTotalSize(level int) int64 {
if o.CompactionTotalSize > 0 {
base = o.CompactionTotalSize
}
- if len(o.CompactionTotalSizeMultiplierPerLevel) > level && o.CompactionTotalSizeMultiplierPerLevel[level] > 0 {
+ if level < len(o.CompactionTotalSizeMultiplierPerLevel) && o.CompactionTotalSizeMultiplierPerLevel[level] > 0 {
mult = o.CompactionTotalSizeMultiplierPerLevel[level]
} else if o.CompactionTotalSizeMultiplier > 0 {
mult = math.Pow(o.CompactionTotalSizeMultiplier, float64(level))
@@ -472,6 +480,20 @@ func (o *Options) GetCompression() Compression {
return o.Compression
}
+func (o *Options) GetDisableBufferPool() bool {
+ if o == nil {
+ return false
+ }
+ return o.DisableBufferPool
+}
+
+func (o *Options) GetDisableBlockCache() bool {
+ if o == nil {
+ return false
+ }
+ return o.DisableBlockCache
+}
+
func (o *Options) GetDisableCompactionBackoff() bool {
if o == nil {
return false
@@ -479,6 +501,13 @@ func (o *Options) GetDisableCompactionBackoff() bool {
return o.DisableCompactionBackoff
}
+func (o *Options) GetDisableLargeBatchTransaction() bool {
+ if o == nil {
+ return false
+ }
+ return o.DisableLargeBatchTransaction
+}
+
func (o *Options) GetErrorIfExist() bool {
if o == nil {
return false
@@ -507,26 +536,11 @@ func (o *Options) GetIteratorSamplingRate() int {
return o.IteratorSamplingRate
}
-func (o *Options) GetMaxMemCompationLevel() int {
- level := DefaultMaxMemCompationLevel
- if o != nil {
- if o.MaxMemCompationLevel > 0 {
- level = o.MaxMemCompationLevel
- } else if o.MaxMemCompationLevel < 0 {
- level = 0
- }
- }
- if level >= o.GetNumLevel() {
- return o.GetNumLevel() - 1
- }
- return level
-}
-
-func (o *Options) GetNumLevel() int {
- if o == nil || o.NumLevel <= 0 {
- return DefaultNumLevel
+func (o *Options) GetNoSync() bool {
+ if o == nil {
+ return false
}
- return o.NumLevel
+ return o.NoSync
}
func (o *Options) GetOpenFilesCacher() Cacher {
@@ -548,6 +562,13 @@ func (o *Options) GetOpenFilesCacheCapacity() int {
return o.OpenFilesCacheCapacity
}
+func (o *Options) GetReadOnly() bool {
+ if o == nil {
+ return false
+ }
+ return o.ReadOnly
+}
+
func (o *Options) GetStrict(strict Strict) bool {
if o == nil || o.Strict == 0 {
return DefaultStrict&strict != 0
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/options.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/options.go
index a3d84ef60..b072b1ac4 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/options.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/options.go
@@ -43,6 +43,8 @@ func (s *session) setOptions(o *opt.Options) {
s.o.cache()
}
+const optCachedLevel = 7
+
type cachedOptions struct {
*opt.Options
@@ -54,15 +56,13 @@ type cachedOptions struct {
}
func (co *cachedOptions) cache() {
- numLevel := co.Options.GetNumLevel()
-
- co.compactionExpandLimit = make([]int, numLevel)
- co.compactionGPOverlaps = make([]int, numLevel)
- co.compactionSourceLimit = make([]int, numLevel)
- co.compactionTableSize = make([]int, numLevel)
- co.compactionTotalSize = make([]int64, numLevel)
+ co.compactionExpandLimit = make([]int, optCachedLevel)
+ co.compactionGPOverlaps = make([]int, optCachedLevel)
+ co.compactionSourceLimit = make([]int, optCachedLevel)
+ co.compactionTableSize = make([]int, optCachedLevel)
+ co.compactionTotalSize = make([]int64, optCachedLevel)
- for level := 0; level < numLevel; level++ {
+ for level := 0; level < optCachedLevel; level++ {
co.compactionExpandLimit[level] = co.Options.GetCompactionExpandLimit(level)
co.compactionGPOverlaps[level] = co.Options.GetCompactionGPOverlaps(level)
co.compactionSourceLimit[level] = co.Options.GetCompactionSourceLimit(level)
@@ -72,21 +72,36 @@ func (co *cachedOptions) cache() {
}
func (co *cachedOptions) GetCompactionExpandLimit(level int) int {
- return co.compactionExpandLimit[level]
+ if level < optCachedLevel {
+ return co.compactionExpandLimit[level]
+ }
+ return co.Options.GetCompactionExpandLimit(level)
}
func (co *cachedOptions) GetCompactionGPOverlaps(level int) int {
- return co.compactionGPOverlaps[level]
+ if level < optCachedLevel {
+ return co.compactionGPOverlaps[level]
+ }
+ return co.Options.GetCompactionGPOverlaps(level)
}
func (co *cachedOptions) GetCompactionSourceLimit(level int) int {
- return co.compactionSourceLimit[level]
+ if level < optCachedLevel {
+ return co.compactionSourceLimit[level]
+ }
+ return co.Options.GetCompactionSourceLimit(level)
}
func (co *cachedOptions) GetCompactionTableSize(level int) int {
- return co.compactionTableSize[level]
+ if level < optCachedLevel {
+ return co.compactionTableSize[level]
+ }
+ return co.Options.GetCompactionTableSize(level)
}
func (co *cachedOptions) GetCompactionTotalSize(level int) int64 {
- return co.compactionTotalSize[level]
+ if level < optCachedLevel {
+ return co.compactionTotalSize[level]
+ }
+ return co.Options.GetCompactionTotalSize(level)
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go
index b3906f7fc..a8d7b54dc 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go
@@ -11,14 +11,11 @@ import (
"io"
"os"
"sync"
- "sync/atomic"
"github.com/syndtr/goleveldb/leveldb/errors"
- "github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/journal"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/storage"
- "github.com/syndtr/goleveldb/leveldb/util"
)
type ErrManifestCorrupted struct {
@@ -30,28 +27,28 @@ func (e *ErrManifestCorrupted) Error() string {
return fmt.Sprintf("leveldb: manifest corrupted (field '%s'): %s", e.Field, e.Reason)
}
-func newErrManifestCorrupted(f storage.File, field, reason string) error {
- return errors.NewErrCorrupted(f, &ErrManifestCorrupted{field, reason})
+func newErrManifestCorrupted(fd storage.FileDesc, field, reason string) error {
+ return errors.NewErrCorrupted(fd, &ErrManifestCorrupted{field, reason})
}
// session represent a persistent database session.
type session struct {
// Need 64-bit alignment.
- stNextFileNum uint64 // current unused file number
- stJournalNum uint64 // current journal file number; need external synchronization
- stPrevJournalNum uint64 // prev journal file number; no longer used; for compatibility with older version of leveldb
+ stNextFileNum int64 // current unused file number
+ stJournalNum int64 // current journal file number; need external synchronization
+ stPrevJournalNum int64 // prev journal file number; no longer used; for compatibility with older version of leveldb
+ stTempFileNum int64
stSeqNum uint64 // last mem compacted seq; need external synchronization
- stTempFileNum uint64
stor storage.Storage
- storLock util.Releaser
+ storLock storage.Lock
o *cachedOptions
icmp *iComparer
tops *tOps
manifest *journal.Writer
manifestWriter storage.Writer
- manifestFile storage.File
+ manifestFd storage.FileDesc
stCompPtrs []iKey // compaction pointers; need external synchronization
stVersion *version // current version
@@ -68,9 +65,8 @@ func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) {
return
}
s = &session{
- stor: stor,
- storLock: storLock,
- stCompPtrs: make([]iKey, o.GetNumLevel()),
+ stor: stor,
+ storLock: storLock,
}
s.setOptions(o)
s.tops = newTableOps(s)
@@ -90,7 +86,6 @@ func (s *session) close() {
}
s.manifest = nil
s.manifestWriter = nil
- s.manifestFile = nil
s.stVersion = nil
}
@@ -111,27 +106,31 @@ func (s *session) recover() (err error) {
if os.IsNotExist(err) {
// Don't return os.ErrNotExist if the underlying storage contains
// other files that belong to LevelDB. So the DB won't get trashed.
- if files, _ := s.stor.GetFiles(storage.TypeAll); len(files) > 0 {
- err = &errors.ErrCorrupted{File: &storage.FileInfo{Type: storage.TypeManifest}, Err: &errors.ErrMissingFiles{}}
+ if fds, _ := s.stor.List(storage.TypeAll); len(fds) > 0 {
+ err = &errors.ErrCorrupted{Fd: storage.FileDesc{Type: storage.TypeManifest}, Err: &errors.ErrMissingFiles{}}
}
}
}()
- m, err := s.stor.GetManifest()
+ fd, err := s.stor.GetMeta()
if err != nil {
return
}
- reader, err := m.Open()
+ reader, err := s.stor.Open(fd)
if err != nil {
return
}
defer reader.Close()
- strict := s.o.GetStrict(opt.StrictManifest)
- jr := journal.NewReader(reader, dropper{s, m}, strict, true)
- staging := s.stVersion.newStaging()
- rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
+ var (
+ // Options.
+ strict = s.o.GetStrict(opt.StrictManifest)
+
+ jr = journal.NewReader(reader, dropper{s, fd}, strict, true)
+ rec = &sessionRecord{}
+ staging = s.stVersion.newStaging()
+ )
for {
var r io.Reader
r, err = jr.Next()
@@ -140,23 +139,23 @@ func (s *session) recover() (err error) {
err = nil
break
}
- return errors.SetFile(err, m)
+ return errors.SetFd(err, fd)
}
err = rec.decode(r)
if err == nil {
// save compact pointers
for _, r := range rec.compPtrs {
- s.stCompPtrs[r.level] = iKey(r.ikey)
+ s.setCompPtr(r.level, iKey(r.ikey))
}
// commit record to version staging
staging.commit(rec)
} else {
- err = errors.SetFile(err, m)
+ err = errors.SetFd(err, fd)
if strict || !errors.IsCorrupted(err) {
return
} else {
- s.logf("manifest error: %v (skipped)", errors.SetFile(err, m))
+ s.logf("manifest error: %v (skipped)", errors.SetFd(err, fd))
}
}
rec.resetCompPtrs()
@@ -166,18 +165,18 @@ func (s *session) recover() (err error) {
switch {
case !rec.has(recComparer):
- return newErrManifestCorrupted(m, "comparer", "missing")
+ return newErrManifestCorrupted(fd, "comparer", "missing")
case rec.comparer != s.icmp.uName():
- return newErrManifestCorrupted(m, "comparer", fmt.Sprintf("mismatch: want '%s', got '%s'", s.icmp.uName(), rec.comparer))
+ return newErrManifestCorrupted(fd, "comparer", fmt.Sprintf("mismatch: want '%s', got '%s'", s.icmp.uName(), rec.comparer))
case !rec.has(recNextFileNum):
- return newErrManifestCorrupted(m, "next-file-num", "missing")
+ return newErrManifestCorrupted(fd, "next-file-num", "missing")
case !rec.has(recJournalNum):
- return newErrManifestCorrupted(m, "journal-file-num", "missing")
+ return newErrManifestCorrupted(fd, "journal-file-num", "missing")
case !rec.has(recSeqNum):
- return newErrManifestCorrupted(m, "seq-num", "missing")
+ return newErrManifestCorrupted(fd, "seq-num", "missing")
}
- s.manifestFile = m
+ s.manifestFd = fd
s.setVersion(staging.finish())
s.setNextFileNum(rec.nextFileNum)
s.recordCommited(rec)
@@ -206,250 +205,3 @@ func (s *session) commit(r *sessionRecord) (err error) {
return
}
-
-// Pick a compaction based on current state; need external synchronization.
-func (s *session) pickCompaction() *compaction {
- v := s.version()
-
- var level int
- var t0 tFiles
- if v.cScore >= 1 {
- level = v.cLevel
- cptr := s.stCompPtrs[level]
- tables := v.tables[level]
- for _, t := range tables {
- if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
- t0 = append(t0, t)
- break
- }
- }
- if len(t0) == 0 {
- t0 = append(t0, tables[0])
- }
- } else {
- if p := atomic.LoadPointer(&v.cSeek); p != nil {
- ts := (*tSet)(p)
- level = ts.level
- t0 = append(t0, ts.table)
- } else {
- v.release()
- return nil
- }
- }
-
- return newCompaction(s, v, level, t0)
-}
-
-// Create compaction from given level and range; need external synchronization.
-func (s *session) getCompactionRange(level int, umin, umax []byte) *compaction {
- v := s.version()
-
- t0 := v.tables[level].getOverlaps(nil, s.icmp, umin, umax, level == 0)
- if len(t0) == 0 {
- v.release()
- return nil
- }
-
- // Avoid compacting too much in one shot in case the range is large.
- // 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 {
- limit := uint64(v.s.o.GetCompactionSourceLimit(level))
- total := uint64(0)
- for i, t := range t0 {
- total += t.size
- if total >= limit {
- s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
- t0 = t0[:i+1]
- break
- }
- }
- }
-
- return newCompaction(s, v, level, t0)
-}
-
-func newCompaction(s *session, v *version, level int, t0 tFiles) *compaction {
- c := &compaction{
- s: s,
- v: v,
- level: level,
- tables: [2]tFiles{t0, nil},
- maxGPOverlaps: uint64(s.o.GetCompactionGPOverlaps(level)),
- tPtrs: make([]int, s.o.GetNumLevel()),
- }
- c.expand()
- c.save()
- return c
-}
-
-// compaction represent a compaction state.
-type compaction struct {
- s *session
- v *version
-
- level int
- tables [2]tFiles
- maxGPOverlaps uint64
-
- gp tFiles
- gpi int
- seenKey bool
- gpOverlappedBytes uint64
- imin, imax iKey
- tPtrs []int
- released bool
-
- snapGPI int
- snapSeenKey bool
- snapGPOverlappedBytes uint64
- snapTPtrs []int
-}
-
-func (c *compaction) save() {
- c.snapGPI = c.gpi
- c.snapSeenKey = c.seenKey
- c.snapGPOverlappedBytes = c.gpOverlappedBytes
- c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...)
-}
-
-func (c *compaction) restore() {
- c.gpi = c.snapGPI
- c.seenKey = c.snapSeenKey
- c.gpOverlappedBytes = c.snapGPOverlappedBytes
- c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...)
-}
-
-func (c *compaction) release() {
- if !c.released {
- c.released = true
- c.v.release()
- }
-}
-
-// Expand compacted tables; need external synchronization.
-func (c *compaction) expand() {
- limit := uint64(c.s.o.GetCompactionExpandLimit(c.level))
- vt0, vt1 := c.v.tables[c.level], c.v.tables[c.level+1]
-
- t0, t1 := c.tables[0], c.tables[1]
- imin, imax := t0.getRange(c.s.icmp)
- // We expand t0 here just incase ukey hop across tables.
- t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.level == 0)
- if len(t0) != len(c.tables[0]) {
- imin, imax = t0.getRange(c.s.icmp)
- }
- t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
- // Get entire range covered by compaction.
- amin, amax := append(t0, t1...).getRange(c.s.icmp)
-
- // See if we can grow the number of inputs in "level" without
- // changing the number of "level+1" files we pick up.
- if len(t1) > 0 {
- exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.level == 0)
- if len(exp0) > len(t0) && t1.size()+exp0.size() < limit {
- xmin, xmax := exp0.getRange(c.s.icmp)
- exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
- if len(exp1) == len(t1) {
- c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
- c.level, c.level+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
- len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
- imin, imax = xmin, xmax
- t0, t1 = exp0, exp1
- amin, amax = append(t0, t1...).getRange(c.s.icmp)
- }
- }
- }
-
- // Compute the set of grandparent files that overlap this compaction
- // (parent == level+1; grandparent == level+2)
- if c.level+2 < c.s.o.GetNumLevel() {
- c.gp = c.v.tables[c.level+2].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
- }
-
- c.tables[0], c.tables[1] = t0, t1
- c.imin, c.imax = imin, imax
-}
-
-// Check whether compaction is trivial.
-func (c *compaction) trivial() bool {
- return len(c.tables[0]) == 1 && len(c.tables[1]) == 0 && c.gp.size() <= c.maxGPOverlaps
-}
-
-func (c *compaction) baseLevelForKey(ukey []byte) bool {
- for level, tables := range c.v.tables[c.level+2:] {
- for c.tPtrs[level] < len(tables) {
- t := tables[c.tPtrs[level]]
- if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
- // We've advanced far enough.
- if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
- // Key falls in this file's range, so definitely not base level.
- return false
- }
- break
- }
- c.tPtrs[level]++
- }
- }
- return true
-}
-
-func (c *compaction) shouldStopBefore(ikey iKey) bool {
- for ; c.gpi < len(c.gp); c.gpi++ {
- gp := c.gp[c.gpi]
- if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
- break
- }
- if c.seenKey {
- c.gpOverlappedBytes += gp.size
- }
- }
- c.seenKey = true
-
- if c.gpOverlappedBytes > c.maxGPOverlaps {
- // Too much overlap for current output; start new output.
- c.gpOverlappedBytes = 0
- return true
- }
- return false
-}
-
-// Creates an iterator.
-func (c *compaction) newIterator() iterator.Iterator {
- // Creates iterator slice.
- icap := len(c.tables)
- if c.level == 0 {
- // Special case for level-0
- icap = len(c.tables[0]) + 1
- }
- its := make([]iterator.Iterator, 0, icap)
-
- // Options.
- ro := &opt.ReadOptions{
- DontFillCache: true,
- Strict: opt.StrictOverride,
- }
- strict := c.s.o.GetStrict(opt.StrictCompaction)
- if strict {
- ro.Strict |= opt.StrictReader
- }
-
- for i, tables := range c.tables {
- if len(tables) == 0 {
- continue
- }
-
- // Level-0 is not sorted and may overlaps each other.
- if c.level+i == 0 {
- for _, t := range tables {
- its = append(its, c.s.tops.newIterator(t, nil, ro))
- }
- } else {
- it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict)
- its = append(its, it)
- }
- }
-
- return iterator.NewMergedIterator(its, c.s.icmp, strict)
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_compaction.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_compaction.go
new file mode 100644
index 000000000..471d68d56
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_compaction.go
@@ -0,0 +1,302 @@
+// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
+// All rights reserved.
+//
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package leveldb
+
+import (
+ "sync/atomic"
+
+ "github.com/syndtr/goleveldb/leveldb/iterator"
+ "github.com/syndtr/goleveldb/leveldb/memdb"
+ "github.com/syndtr/goleveldb/leveldb/opt"
+)
+
+func (s *session) pickMemdbLevel(umin, umax []byte, maxLevel int) int {
+ v := s.version()
+ defer v.release()
+ return v.pickMemdbLevel(umin, umax, maxLevel)
+}
+
+func (s *session) flushMemdb(rec *sessionRecord, mdb *memdb.DB, maxLevel int) (int, error) {
+ // Create sorted table.
+ iter := mdb.NewIterator(nil)
+ defer iter.Release()
+ t, n, err := s.tops.createFrom(iter)
+ if err != nil {
+ return 0, err
+ }
+
+ // Pick level other than zero can cause compaction issue with large
+ // bulk insert and delete on strictly incrementing key-space. The
+ // problem is that the small deletion markers trapped at lower level,
+ // while key/value entries keep growing at higher level. Since the
+ // key-space is strictly incrementing it will not overlaps with
+ // higher level, thus maximum possible level is always picked, while
+ // overlapping deletion marker pushed into lower level.
+ // See: https://github.com/syndtr/goleveldb/issues/127.
+ flushLevel := s.pickMemdbLevel(t.imin.ukey(), t.imax.ukey(), maxLevel)
+ rec.addTableFile(flushLevel, t)
+
+ s.logf("memdb@flush created L%d@%d N·%d S·%s %q:%q", flushLevel, t.fd.Num, n, shortenb(int(t.size)), t.imin, t.imax)
+ return flushLevel, nil
+}
+
+// Pick a compaction based on current state; need external synchronization.
+func (s *session) pickCompaction() *compaction {
+ v := s.version()
+
+ var sourceLevel int
+ var t0 tFiles
+ if v.cScore >= 1 {
+ sourceLevel = v.cLevel
+ cptr := s.getCompPtr(sourceLevel)
+ tables := v.levels[sourceLevel]
+ for _, t := range tables {
+ if cptr == nil || s.icmp.Compare(t.imax, cptr) > 0 {
+ t0 = append(t0, t)
+ break
+ }
+ }
+ if len(t0) == 0 {
+ t0 = append(t0, tables[0])
+ }
+ } else {
+ if p := atomic.LoadPointer(&v.cSeek); p != nil {
+ ts := (*tSet)(p)
+ sourceLevel = ts.level
+ t0 = append(t0, ts.table)
+ } else {
+ v.release()
+ return nil
+ }
+ }
+
+ return newCompaction(s, v, sourceLevel, t0)
+}
+
+// Create compaction from given level and range; need external synchronization.
+func (s *session) getCompactionRange(sourceLevel int, umin, umax []byte, noLimit bool) *compaction {
+ v := s.version()
+
+ if sourceLevel >= len(v.levels) {
+ v.release()
+ return nil
+ }
+
+ t0 := v.levels[sourceLevel].getOverlaps(nil, s.icmp, umin, umax, sourceLevel == 0)
+ if len(t0) == 0 {
+ v.release()
+ return nil
+ }
+
+ // Avoid compacting too much in one shot in case the range is large.
+ // 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 !noLimit && sourceLevel > 0 {
+ limit := int64(v.s.o.GetCompactionSourceLimit(sourceLevel))
+ total := int64(0)
+ for i, t := range t0 {
+ total += t.size
+ if total >= limit {
+ s.logf("table@compaction limiting F·%d -> F·%d", len(t0), i+1)
+ t0 = t0[:i+1]
+ break
+ }
+ }
+ }
+
+ return newCompaction(s, v, sourceLevel, t0)
+}
+
+func newCompaction(s *session, v *version, sourceLevel int, t0 tFiles) *compaction {
+ c := &compaction{
+ s: s,
+ v: v,
+ sourceLevel: sourceLevel,
+ levels: [2]tFiles{t0, nil},
+ maxGPOverlaps: int64(s.o.GetCompactionGPOverlaps(sourceLevel)),
+ tPtrs: make([]int, len(v.levels)),
+ }
+ c.expand()
+ c.save()
+ return c
+}
+
+// compaction represent a compaction state.
+type compaction struct {
+ s *session
+ v *version
+
+ sourceLevel int
+ levels [2]tFiles
+ maxGPOverlaps int64
+
+ gp tFiles
+ gpi int
+ seenKey bool
+ gpOverlappedBytes int64
+ imin, imax iKey
+ tPtrs []int
+ released bool
+
+ snapGPI int
+ snapSeenKey bool
+ snapGPOverlappedBytes int64
+ snapTPtrs []int
+}
+
+func (c *compaction) save() {
+ c.snapGPI = c.gpi
+ c.snapSeenKey = c.seenKey
+ c.snapGPOverlappedBytes = c.gpOverlappedBytes
+ c.snapTPtrs = append(c.snapTPtrs[:0], c.tPtrs...)
+}
+
+func (c *compaction) restore() {
+ c.gpi = c.snapGPI
+ c.seenKey = c.snapSeenKey
+ c.gpOverlappedBytes = c.snapGPOverlappedBytes
+ c.tPtrs = append(c.tPtrs[:0], c.snapTPtrs...)
+}
+
+func (c *compaction) release() {
+ if !c.released {
+ c.released = true
+ c.v.release()
+ }
+}
+
+// Expand compacted tables; need external synchronization.
+func (c *compaction) expand() {
+ limit := int64(c.s.o.GetCompactionExpandLimit(c.sourceLevel))
+ vt0 := c.v.levels[c.sourceLevel]
+ vt1 := tFiles{}
+ if level := c.sourceLevel + 1; level < len(c.v.levels) {
+ vt1 = c.v.levels[level]
+ }
+
+ t0, t1 := c.levels[0], c.levels[1]
+ imin, imax := t0.getRange(c.s.icmp)
+ // We expand t0 here just incase ukey hop across tables.
+ t0 = vt0.getOverlaps(t0, c.s.icmp, imin.ukey(), imax.ukey(), c.sourceLevel == 0)
+ if len(t0) != len(c.levels[0]) {
+ imin, imax = t0.getRange(c.s.icmp)
+ }
+ t1 = vt1.getOverlaps(t1, c.s.icmp, imin.ukey(), imax.ukey(), false)
+ // Get entire range covered by compaction.
+ amin, amax := append(t0, t1...).getRange(c.s.icmp)
+
+ // See if we can grow the number of inputs in "sourceLevel" without
+ // changing the number of "sourceLevel+1" files we pick up.
+ if len(t1) > 0 {
+ exp0 := vt0.getOverlaps(nil, c.s.icmp, amin.ukey(), amax.ukey(), c.sourceLevel == 0)
+ if len(exp0) > len(t0) && t1.size()+exp0.size() < limit {
+ xmin, xmax := exp0.getRange(c.s.icmp)
+ exp1 := vt1.getOverlaps(nil, c.s.icmp, xmin.ukey(), xmax.ukey(), false)
+ if len(exp1) == len(t1) {
+ c.s.logf("table@compaction expanding L%d+L%d (F·%d S·%s)+(F·%d S·%s) -> (F·%d S·%s)+(F·%d S·%s)",
+ c.sourceLevel, c.sourceLevel+1, len(t0), shortenb(int(t0.size())), len(t1), shortenb(int(t1.size())),
+ len(exp0), shortenb(int(exp0.size())), len(exp1), shortenb(int(exp1.size())))
+ imin, imax = xmin, xmax
+ t0, t1 = exp0, exp1
+ amin, amax = append(t0, t1...).getRange(c.s.icmp)
+ }
+ }
+ }
+
+ // Compute the set of grandparent files that overlap this compaction
+ // (parent == sourceLevel+1; grandparent == sourceLevel+2)
+ if level := c.sourceLevel + 2; level < len(c.v.levels) {
+ c.gp = c.v.levels[level].getOverlaps(c.gp, c.s.icmp, amin.ukey(), amax.ukey(), false)
+ }
+
+ c.levels[0], c.levels[1] = t0, t1
+ c.imin, c.imax = imin, imax
+}
+
+// Check whether compaction is trivial.
+func (c *compaction) trivial() bool {
+ return len(c.levels[0]) == 1 && len(c.levels[1]) == 0 && c.gp.size() <= c.maxGPOverlaps
+}
+
+func (c *compaction) baseLevelForKey(ukey []byte) bool {
+ for level := c.sourceLevel + 2; level < len(c.v.levels); level++ {
+ tables := c.v.levels[level]
+ for c.tPtrs[level] < len(tables) {
+ t := tables[c.tPtrs[level]]
+ if c.s.icmp.uCompare(ukey, t.imax.ukey()) <= 0 {
+ // We've advanced far enough.
+ if c.s.icmp.uCompare(ukey, t.imin.ukey()) >= 0 {
+ // Key falls in this file's range, so definitely not base level.
+ return false
+ }
+ break
+ }
+ c.tPtrs[level]++
+ }
+ }
+ return true
+}
+
+func (c *compaction) shouldStopBefore(ikey iKey) bool {
+ for ; c.gpi < len(c.gp); c.gpi++ {
+ gp := c.gp[c.gpi]
+ if c.s.icmp.Compare(ikey, gp.imax) <= 0 {
+ break
+ }
+ if c.seenKey {
+ c.gpOverlappedBytes += gp.size
+ }
+ }
+ c.seenKey = true
+
+ if c.gpOverlappedBytes > c.maxGPOverlaps {
+ // Too much overlap for current output; start new output.
+ c.gpOverlappedBytes = 0
+ return true
+ }
+ return false
+}
+
+// Creates an iterator.
+func (c *compaction) newIterator() iterator.Iterator {
+ // Creates iterator slice.
+ icap := len(c.levels)
+ if c.sourceLevel == 0 {
+ // Special case for level-0.
+ icap = len(c.levels[0]) + 1
+ }
+ its := make([]iterator.Iterator, 0, icap)
+
+ // Options.
+ ro := &opt.ReadOptions{
+ DontFillCache: true,
+ Strict: opt.StrictOverride,
+ }
+ strict := c.s.o.GetStrict(opt.StrictCompaction)
+ if strict {
+ ro.Strict |= opt.StrictReader
+ }
+
+ for i, tables := range c.levels {
+ if len(tables) == 0 {
+ continue
+ }
+
+ // Level-0 is not sorted and may overlaps each other.
+ if c.sourceLevel+i == 0 {
+ for _, t := range tables {
+ its = append(its, c.s.tops.newIterator(t, nil, ro))
+ }
+ } else {
+ it := iterator.NewIndexedIterator(tables.newIndexIterator(c.s.tops, c.s.icmp, nil, ro), strict)
+ its = append(its, it)
+ }
+ }
+
+ return iterator.NewMergedIterator(its, c.s.icmp, strict)
+}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record.go
index 1bdcc68f5..9802e1a55 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record.go
@@ -13,6 +13,7 @@ import (
"strings"
"github.com/syndtr/goleveldb/leveldb/errors"
+ "github.com/syndtr/goleveldb/leveldb/storage"
)
type byteReader interface {
@@ -40,25 +41,23 @@ type cpRecord struct {
type atRecord struct {
level int
- num uint64
- size uint64
+ num int64
+ size int64
imin iKey
imax iKey
}
type dtRecord struct {
level int
- num uint64
+ num int64
}
type sessionRecord struct {
- numLevel int
-
hasRec int
comparer string
- journalNum uint64
- prevJournalNum uint64
- nextFileNum uint64
+ journalNum int64
+ prevJournalNum int64
+ nextFileNum int64
seqNum uint64
compPtrs []cpRecord
addedTables []atRecord
@@ -77,17 +76,17 @@ func (p *sessionRecord) setComparer(name string) {
p.comparer = name
}
-func (p *sessionRecord) setJournalNum(num uint64) {
+func (p *sessionRecord) setJournalNum(num int64) {
p.hasRec |= 1 << recJournalNum
p.journalNum = num
}
-func (p *sessionRecord) setPrevJournalNum(num uint64) {
+func (p *sessionRecord) setPrevJournalNum(num int64) {
p.hasRec |= 1 << recPrevJournalNum
p.prevJournalNum = num
}
-func (p *sessionRecord) setNextFileNum(num uint64) {
+func (p *sessionRecord) setNextFileNum(num int64) {
p.hasRec |= 1 << recNextFileNum
p.nextFileNum = num
}
@@ -107,13 +106,13 @@ func (p *sessionRecord) resetCompPtrs() {
p.compPtrs = p.compPtrs[:0]
}
-func (p *sessionRecord) addTable(level int, num, size uint64, imin, imax iKey) {
+func (p *sessionRecord) addTable(level int, num, size int64, imin, imax iKey) {
p.hasRec |= 1 << recAddTable
p.addedTables = append(p.addedTables, atRecord{level, num, size, imin, imax})
}
func (p *sessionRecord) addTableFile(level int, t *tFile) {
- p.addTable(level, t.file.Num(), t.size, t.imin, t.imax)
+ p.addTable(level, t.fd.Num, t.size, t.imin, t.imax)
}
func (p *sessionRecord) resetAddedTables() {
@@ -121,7 +120,7 @@ func (p *sessionRecord) resetAddedTables() {
p.addedTables = p.addedTables[:0]
}
-func (p *sessionRecord) delTable(level int, num uint64) {
+func (p *sessionRecord) delTable(level int, num int64) {
p.hasRec |= 1 << recDelTable
p.deletedTables = append(p.deletedTables, dtRecord{level, num})
}
@@ -139,6 +138,13 @@ func (p *sessionRecord) putUvarint(w io.Writer, x uint64) {
_, p.err = w.Write(p.scratch[:n])
}
+func (p *sessionRecord) putVarint(w io.Writer, x int64) {
+ if x < 0 {
+ panic("invalid negative value")
+ }
+ p.putUvarint(w, uint64(x))
+}
+
func (p *sessionRecord) putBytes(w io.Writer, x []byte) {
if p.err != nil {
return
@@ -158,11 +164,11 @@ func (p *sessionRecord) encode(w io.Writer) error {
}
if p.has(recJournalNum) {
p.putUvarint(w, recJournalNum)
- p.putUvarint(w, p.journalNum)
+ p.putVarint(w, p.journalNum)
}
if p.has(recNextFileNum) {
p.putUvarint(w, recNextFileNum)
- p.putUvarint(w, p.nextFileNum)
+ p.putVarint(w, p.nextFileNum)
}
if p.has(recSeqNum) {
p.putUvarint(w, recSeqNum)
@@ -176,13 +182,13 @@ func (p *sessionRecord) encode(w io.Writer) error {
for _, r := range p.deletedTables {
p.putUvarint(w, recDelTable)
p.putUvarint(w, uint64(r.level))
- p.putUvarint(w, r.num)
+ p.putVarint(w, r.num)
}
for _, r := range p.addedTables {
p.putUvarint(w, recAddTable)
p.putUvarint(w, uint64(r.level))
- p.putUvarint(w, r.num)
- p.putUvarint(w, r.size)
+ p.putVarint(w, r.num)
+ p.putVarint(w, r.size)
p.putBytes(w, r.imin)
p.putBytes(w, r.imax)
}
@@ -196,9 +202,9 @@ func (p *sessionRecord) readUvarintMayEOF(field string, r io.ByteReader, mayEOF
x, err := binary.ReadUvarint(r)
if err != nil {
if err == io.ErrUnexpectedEOF || (mayEOF == false && err == io.EOF) {
- p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "short read"})
+ p.err = errors.NewErrCorrupted(storage.FileDesc{}, &ErrManifestCorrupted{field, "short read"})
} else if strings.HasPrefix(err.Error(), "binary:") {
- p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, err.Error()})
+ p.err = errors.NewErrCorrupted(storage.FileDesc{}, &ErrManifestCorrupted{field, err.Error()})
} else {
p.err = err
}
@@ -211,6 +217,14 @@ func (p *sessionRecord) readUvarint(field string, r io.ByteReader) uint64 {
return p.readUvarintMayEOF(field, r, false)
}
+func (p *sessionRecord) readVarint(field string, r io.ByteReader) int64 {
+ x := int64(p.readUvarintMayEOF(field, r, false))
+ if x < 0 {
+ p.err = errors.NewErrCorrupted(storage.FileDesc{}, &ErrManifestCorrupted{field, "invalid negative value"})
+ }
+ return x
+}
+
func (p *sessionRecord) readBytes(field string, r byteReader) []byte {
if p.err != nil {
return nil
@@ -223,7 +237,7 @@ func (p *sessionRecord) readBytes(field string, r byteReader) []byte {
_, p.err = io.ReadFull(r, x)
if p.err != nil {
if p.err == io.ErrUnexpectedEOF {
- p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "short read"})
+ p.err = errors.NewErrCorrupted(storage.FileDesc{}, &ErrManifestCorrupted{field, "short read"})
}
return nil
}
@@ -238,10 +252,6 @@ func (p *sessionRecord) readLevel(field string, r io.ByteReader) int {
if p.err != nil {
return 0
}
- if x >= uint64(p.numLevel) {
- p.err = errors.NewErrCorrupted(nil, &ErrManifestCorrupted{field, "invalid level number"})
- return 0
- }
return int(x)
}
@@ -266,17 +276,17 @@ func (p *sessionRecord) decode(r io.Reader) error {
p.setComparer(string(x))
}
case recJournalNum:
- x := p.readUvarint("journal-num", br)
+ x := p.readVarint("journal-num", br)
if p.err == nil {
p.setJournalNum(x)
}
case recPrevJournalNum:
- x := p.readUvarint("prev-journal-num", br)
+ x := p.readVarint("prev-journal-num", br)
if p.err == nil {
p.setPrevJournalNum(x)
}
case recNextFileNum:
- x := p.readUvarint("next-file-num", br)
+ x := p.readVarint("next-file-num", br)
if p.err == nil {
p.setNextFileNum(x)
}
@@ -293,8 +303,8 @@ func (p *sessionRecord) decode(r io.Reader) error {
}
case recAddTable:
level := p.readLevel("add-table.level", br)
- num := p.readUvarint("add-table.num", br)
- size := p.readUvarint("add-table.size", br)
+ num := p.readVarint("add-table.num", br)
+ size := p.readVarint("add-table.size", br)
imin := p.readBytes("add-table.imin", br)
imax := p.readBytes("add-table.imax", br)
if p.err == nil {
@@ -302,7 +312,7 @@ func (p *sessionRecord) decode(r io.Reader) error {
}
case recDelTable:
level := p.readLevel("del-table.level", br)
- num := p.readUvarint("del-table.num", br)
+ num := p.readVarint("del-table.num", br)
if p.err == nil {
p.delTable(level, num)
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go
deleted file mode 100644
index c0c035ae3..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_record_test.go
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- "bytes"
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/opt"
-)
-
-func decodeEncode(v *sessionRecord) (res bool, err error) {
- b := new(bytes.Buffer)
- err = v.encode(b)
- if err != nil {
- return
- }
- v2 := &sessionRecord{numLevel: opt.DefaultNumLevel}
- err = v.decode(b)
- if err != nil {
- return
- }
- b2 := new(bytes.Buffer)
- err = v2.encode(b2)
- if err != nil {
- return
- }
- return bytes.Equal(b.Bytes(), b2.Bytes()), nil
-}
-
-func TestSessionRecord_EncodeDecode(t *testing.T) {
- big := uint64(1) << 50
- v := &sessionRecord{numLevel: opt.DefaultNumLevel}
- i := uint64(0)
- test := func() {
- res, err := decodeEncode(v)
- if err != nil {
- t.Fatalf("error when testing encode/decode sessionRecord: %v", err)
- }
- if !res {
- t.Error("encode/decode test failed at iteration:", i)
- }
- }
-
- for ; i < 4; i++ {
- test()
- v.addTable(3, big+300+i, big+400+i,
- newIkey([]byte("foo"), big+500+1, ktVal),
- newIkey([]byte("zoo"), big+600+1, ktDel))
- v.delTable(4, big+700+i)
- v.addCompPtr(int(i), newIkey([]byte("x"), big+900+1, ktVal))
- }
-
- v.setComparer("foo")
- v.setJournalNum(big + 100)
- v.setPrevJournalNum(big + 99)
- v.setNextFileNum(big + 200)
- v.setSeqNum(big + 1000)
- test()
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_util.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_util.go
index 007c02cde..e4fa98d92 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_util.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session_util.go
@@ -17,15 +17,15 @@ import (
// Logging.
type dropper struct {
- s *session
- file storage.File
+ s *session
+ fd storage.FileDesc
}
func (d dropper) Drop(err error) {
if e, ok := err.(*journal.ErrCorrupted); ok {
- d.s.logf("journal@drop %s-%d S·%s %q", d.file.Type(), d.file.Num(), shortenb(e.Size), e.Reason)
+ d.s.logf("journal@drop %s-%d S·%s %q", d.fd.Type, d.fd.Num, shortenb(e.Size), e.Reason)
} else {
- d.s.logf("journal@drop %s-%d %q", d.file.Type(), d.file.Num(), err)
+ d.s.logf("journal@drop %s-%d %q", d.fd.Type, d.fd.Num, err)
}
}
@@ -34,25 +34,9 @@ func (s *session) logf(format string, v ...interface{}) { s.stor.Log(fmt.Sprintf
// File utils.
-func (s *session) getJournalFile(num uint64) storage.File {
- return s.stor.GetFile(num, storage.TypeJournal)
-}
-
-func (s *session) getTableFile(num uint64) storage.File {
- return s.stor.GetFile(num, storage.TypeTable)
-}
-
-func (s *session) getFiles(t storage.FileType) ([]storage.File, error) {
- return s.stor.GetFiles(t)
-}
-
-func (s *session) newTemp() storage.File {
- num := atomic.AddUint64(&s.stTempFileNum, 1) - 1
- return s.stor.GetFile(num, storage.TypeTemp)
-}
-
-func (s *session) tableFileFromRecord(r atRecord) *tFile {
- return newTableFile(s.getTableFile(r.num), r.size, r.imin, r.imax)
+func (s *session) newTemp() storage.FileDesc {
+ num := atomic.AddInt64(&s.stTempFileNum, 1) - 1
+ return storage.FileDesc{storage.TypeTemp, num}
}
// Session state.
@@ -80,47 +64,65 @@ func (s *session) setVersion(v *version) {
}
// Get current unused file number.
-func (s *session) nextFileNum() uint64 {
- return atomic.LoadUint64(&s.stNextFileNum)
+func (s *session) nextFileNum() int64 {
+ return atomic.LoadInt64(&s.stNextFileNum)
}
// Set current unused file number to num.
-func (s *session) setNextFileNum(num uint64) {
- atomic.StoreUint64(&s.stNextFileNum, num)
+func (s *session) setNextFileNum(num int64) {
+ atomic.StoreInt64(&s.stNextFileNum, num)
}
// Mark file number as used.
-func (s *session) markFileNum(num uint64) {
+func (s *session) markFileNum(num int64) {
nextFileNum := num + 1
for {
old, x := s.stNextFileNum, nextFileNum
if old > x {
x = old
}
- if atomic.CompareAndSwapUint64(&s.stNextFileNum, old, x) {
+ if atomic.CompareAndSwapInt64(&s.stNextFileNum, old, x) {
break
}
}
}
// Allocate a file number.
-func (s *session) allocFileNum() uint64 {
- return atomic.AddUint64(&s.stNextFileNum, 1) - 1
+func (s *session) allocFileNum() int64 {
+ return atomic.AddInt64(&s.stNextFileNum, 1) - 1
}
// Reuse given file number.
-func (s *session) reuseFileNum(num uint64) {
+func (s *session) reuseFileNum(num int64) {
for {
old, x := s.stNextFileNum, num
if old != x+1 {
x = old
}
- if atomic.CompareAndSwapUint64(&s.stNextFileNum, old, x) {
+ if atomic.CompareAndSwapInt64(&s.stNextFileNum, old, x) {
break
}
}
}
+// Set compaction ptr at given level; need external synchronization.
+func (s *session) setCompPtr(level int, ik iKey) {
+ if level >= len(s.stCompPtrs) {
+ newCompPtrs := make([]iKey, level+1)
+ copy(newCompPtrs, s.stCompPtrs)
+ s.stCompPtrs = newCompPtrs
+ }
+ s.stCompPtrs[level] = append(iKey{}, ik...)
+}
+
+// Get compaction ptr at given level; need external synchronization.
+func (s *session) getCompPtr(level int) iKey {
+ if level >= len(s.stCompPtrs) {
+ return nil
+ }
+ return s.stCompPtrs[level]
+}
+
// Manifest related utils.
// Fill given session record obj with current states; need external
@@ -149,29 +151,28 @@ func (s *session) fillRecord(r *sessionRecord, snapshot bool) {
// Mark if record has been committed, this will update session state;
// need external synchronization.
-func (s *session) recordCommited(r *sessionRecord) {
- if r.has(recJournalNum) {
- s.stJournalNum = r.journalNum
+func (s *session) recordCommited(rec *sessionRecord) {
+ if rec.has(recJournalNum) {
+ s.stJournalNum = rec.journalNum
}
- if r.has(recPrevJournalNum) {
- s.stPrevJournalNum = r.prevJournalNum
+ if rec.has(recPrevJournalNum) {
+ s.stPrevJournalNum = rec.prevJournalNum
}
- if r.has(recSeqNum) {
- s.stSeqNum = r.seqNum
+ if rec.has(recSeqNum) {
+ s.stSeqNum = rec.seqNum
}
- for _, p := range r.compPtrs {
- s.stCompPtrs[p.level] = iKey(p.ikey)
+ for _, r := range rec.compPtrs {
+ s.setCompPtr(r.level, iKey(r.ikey))
}
}
// Create a new manifest file; need external synchronization.
func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
- num := s.allocFileNum()
- file := s.stor.GetFile(num, storage.TypeManifest)
- writer, err := file.Create()
+ fd := storage.FileDesc{storage.TypeManifest, s.allocFileNum()}
+ writer, err := s.stor.Create(fd)
if err != nil {
return
}
@@ -182,7 +183,7 @@ func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
defer v.release()
}
if rec == nil {
- rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
+ rec = &sessionRecord{}
}
s.fillRecord(rec, true)
v.fillRecord(rec)
@@ -196,16 +197,16 @@ func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
if s.manifestWriter != nil {
s.manifestWriter.Close()
}
- if s.manifestFile != nil {
- s.manifestFile.Remove()
+ if !s.manifestFd.Nil() {
+ s.stor.Remove(s.manifestFd)
}
- s.manifestFile = file
+ s.manifestFd = fd
s.manifestWriter = writer
s.manifest = jw
} else {
writer.Close()
- file.Remove()
- s.reuseFileNum(num)
+ s.stor.Remove(fd)
+ s.reuseFileNum(fd.Num)
}
}()
@@ -221,7 +222,7 @@ func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
if err != nil {
return
}
- err = s.stor.SetManifest(file)
+ err = s.stor.SetMeta(fd)
return
}
@@ -240,9 +241,11 @@ func (s *session) flushManifest(rec *sessionRecord) (err error) {
if err != nil {
return
}
- err = s.manifestWriter.Sync()
- if err != nil {
- return
+ if !s.o.GetNoSync() {
+ err = s.manifestWriter.Sync()
+ if err != nil {
+ return
+ }
}
s.recordCommited(rec)
return
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go
index 46cc9d070..cbe1dc103 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage.go
@@ -17,11 +17,12 @@ import (
"strings"
"sync"
"time"
-
- "github.com/syndtr/goleveldb/leveldb/util"
)
-var errFileOpen = errors.New("leveldb/storage: file still open")
+var (
+ errFileOpen = errors.New("leveldb/storage: file still open")
+ errReadOnly = errors.New("leveldb/storage: storage is read-only")
+)
type fileLock interface {
release() error
@@ -32,40 +33,52 @@ type fileStorageLock struct {
}
func (lock *fileStorageLock) Release() {
- fs := lock.fs
- fs.mu.Lock()
- defer fs.mu.Unlock()
- if fs.slock == lock {
- fs.slock = nil
+ if lock.fs != nil {
+ lock.fs.mu.Lock()
+ defer lock.fs.mu.Unlock()
+ if lock.fs.slock == lock {
+ lock.fs.slock = nil
+ }
}
- return
}
+const logSizeThreshold = 1024 * 1024 // 1 MiB
+
// fileStorage is a file-system backed storage.
type fileStorage struct {
- path string
-
- mu sync.Mutex
- flock fileLock
- slock *fileStorageLock
- logw *os.File
- buf []byte
+ path string
+ readOnly bool
+
+ mu sync.Mutex
+ flock fileLock
+ slock *fileStorageLock
+ logw *os.File
+ logSize int64
+ buf []byte
// Opened file counter; if open < 0 means closed.
open int
day int
}
// OpenFile returns a new filesytem-backed storage implementation with the given
-// path. This also hold a file lock, so any subsequent attempt to open the same
-// path will fail.
+// path. This also acquire a file lock, so any subsequent attempt to open the
+// same path will fail.
//
// The storage must be closed after use, by calling Close method.
-func OpenFile(path string) (Storage, error) {
- if err := os.MkdirAll(path, 0755); err != nil {
+func OpenFile(path string, readOnly bool) (Storage, error) {
+ if fi, err := os.Stat(path); err == nil {
+ if !fi.IsDir() {
+ return nil, fmt.Errorf("leveldb/storage: open %s: not a directory", path)
+ }
+ } else if os.IsNotExist(err) && !readOnly {
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return nil, err
+ }
+ } else {
return nil, err
}
- flock, err := newFileLock(filepath.Join(path, "LOCK"))
+ flock, err := newFileLock(filepath.Join(path, "LOCK"), readOnly)
if err != nil {
return nil, err
}
@@ -76,23 +89,42 @@ func OpenFile(path string) (Storage, error) {
}
}()
- rename(filepath.Join(path, "LOG"), filepath.Join(path, "LOG.old"))
- logw, err := os.OpenFile(filepath.Join(path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
- if err != nil {
- return nil, err
+ var (
+ logw *os.File
+ logSize int64
+ )
+ if !readOnly {
+ logw, err = os.OpenFile(filepath.Join(path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
+ if err != nil {
+ return nil, err
+ }
+ logSize, err = logw.Seek(0, os.SEEK_END)
+ if err != nil {
+ logw.Close()
+ return nil, err
+ }
}
- fs := &fileStorage{path: path, flock: flock, logw: logw}
+ fs := &fileStorage{
+ path: path,
+ readOnly: readOnly,
+ flock: flock,
+ logw: logw,
+ logSize: logSize,
+ }
runtime.SetFinalizer(fs, (*fileStorage).Close)
return fs, nil
}
-func (fs *fileStorage) Lock() (util.Releaser, error) {
+func (fs *fileStorage) Lock() (Lock, error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
return nil, ErrClosed
}
+ if fs.readOnly {
+ return &fileStorageLock{}, nil
+ }
if fs.slock != nil {
return nil, ErrLocked
}
@@ -101,7 +133,7 @@ func (fs *fileStorage) Lock() (util.Releaser, error) {
}
func itoa(buf []byte, i int, wid int) []byte {
- var u uint = uint(i)
+ u := uint(i)
if u == 0 && wid <= 1 {
return append(buf, '0')
}
@@ -126,6 +158,22 @@ func (fs *fileStorage) printDay(t time.Time) {
}
func (fs *fileStorage) doLog(t time.Time, str string) {
+ if fs.logSize > logSizeThreshold {
+ // Rotate log file.
+ fs.logw.Close()
+ fs.logw = nil
+ fs.logSize = 0
+ rename(filepath.Join(fs.path, "LOG"), filepath.Join(fs.path, "LOG.old"))
+ }
+ if fs.logw == nil {
+ var err error
+ fs.logw, err = os.OpenFile(filepath.Join(fs.path, "LOG"), os.O_WRONLY|os.O_CREATE, 0644)
+ if err != nil {
+ return
+ }
+ // Force printDay on new log file.
+ fs.day = 0
+ }
fs.printDay(t)
hour, min, sec := t.Clock()
msec := t.Nanosecond() / 1e3
@@ -145,65 +193,71 @@ func (fs *fileStorage) doLog(t time.Time, str string) {
}
func (fs *fileStorage) Log(str string) {
- t := time.Now()
- fs.mu.Lock()
- defer fs.mu.Unlock()
- if fs.open < 0 {
- return
+ if !fs.readOnly {
+ t := time.Now()
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ if fs.open < 0 {
+ return
+ }
+ fs.doLog(t, str)
}
- fs.doLog(t, str)
}
func (fs *fileStorage) log(str string) {
- fs.doLog(time.Now(), str)
+ if !fs.readOnly {
+ fs.doLog(time.Now(), str)
+ }
}
-func (fs *fileStorage) GetFile(num uint64, t FileType) File {
- return &file{fs: fs, num: num, t: t}
-}
+func (fs *fileStorage) SetMeta(fd FileDesc) (err error) {
+ if !FileDescOk(fd) {
+ return ErrInvalidFile
+ }
+ if fs.readOnly {
+ return errReadOnly
+ }
-func (fs *fileStorage) GetFiles(t FileType) (ff []File, err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
- return nil, ErrClosed
+ return ErrClosed
}
- dir, err := os.Open(fs.path)
+ defer func() {
+ if err != nil {
+ fs.log(fmt.Sprintf("CURRENT: %v", err))
+ }
+ }()
+ path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
+ w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return
}
- fnn, err := dir.Readdirnames(0)
- // Close the dir first before checking for Readdirnames error.
- if err := dir.Close(); err != nil {
- fs.log(fmt.Sprintf("close dir: %v", err))
+ _, err = fmt.Fprintln(w, fsGenName(fd))
+ // Close the file first.
+ if cerr := w.Close(); cerr != nil {
+ fs.log(fmt.Sprintf("close CURRENT.%d: %v", fd.Num, cerr))
}
if err != nil {
return
}
- f := &file{fs: fs}
- for _, fn := range fnn {
- if f.parse(fn) && (f.t&t) != 0 {
- ff = append(ff, f)
- f = &file{fs: fs}
- }
- }
- return
+ return rename(path, filepath.Join(fs.path, "CURRENT"))
}
-func (fs *fileStorage) GetManifest() (f File, err error) {
+func (fs *fileStorage) GetMeta() (fd FileDesc, err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
- return nil, ErrClosed
+ return FileDesc{}, ErrClosed
}
dir, err := os.Open(fs.path)
if err != nil {
return
}
- fnn, err := dir.Readdirnames(0)
+ names, err := dir.Readdirnames(0)
// Close the dir first before checking for Readdirnames error.
- if err := dir.Close(); err != nil {
- fs.log(fmt.Sprintf("close dir: %v", err))
+ if ce := dir.Close(); ce != nil {
+ fs.log(fmt.Sprintf("close dir: %v", ce))
}
if err != nil {
return
@@ -212,55 +266,64 @@ func (fs *fileStorage) GetManifest() (f File, err error) {
var rem []string
var pend bool
var cerr error
- for _, fn := range fnn {
- if strings.HasPrefix(fn, "CURRENT") {
- pend1 := len(fn) > 7
+ for _, name := range names {
+ if strings.HasPrefix(name, "CURRENT") {
+ pend1 := len(name) > 7
+ var pendNum int64
// Make sure it is valid name for a CURRENT file, otherwise skip it.
if pend1 {
- if fn[7] != '.' || len(fn) < 9 {
- fs.log(fmt.Sprintf("skipping %s: invalid file name", fn))
+ if name[7] != '.' || len(name) < 9 {
+ fs.log(fmt.Sprintf("skipping %s: invalid file name", name))
continue
}
- if _, e1 := strconv.ParseUint(fn[8:], 10, 0); e1 != nil {
- fs.log(fmt.Sprintf("skipping %s: invalid file num: %v", fn, e1))
+ var e1 error
+ if pendNum, e1 = strconv.ParseInt(name[8:], 10, 0); e1 != nil {
+ fs.log(fmt.Sprintf("skipping %s: invalid file num: %v", name, e1))
continue
}
}
- path := filepath.Join(fs.path, fn)
+ path := filepath.Join(fs.path, name)
r, e1 := os.OpenFile(path, os.O_RDONLY, 0)
if e1 != nil {
- return nil, e1
+ return FileDesc{}, e1
}
b, e1 := ioutil.ReadAll(r)
if e1 != nil {
r.Close()
- return nil, e1
+ return FileDesc{}, e1
}
- f1 := &file{fs: fs}
- if len(b) < 1 || b[len(b)-1] != '\n' || !f1.parse(string(b[:len(b)-1])) {
- fs.log(fmt.Sprintf("skipping %s: corrupted or incomplete", fn))
+ var fd1 FileDesc
+ if len(b) < 1 || b[len(b)-1] != '\n' || !fsParseNamePtr(string(b[:len(b)-1]), &fd1) {
+ fs.log(fmt.Sprintf("skipping %s: corrupted or incomplete", name))
if pend1 {
- rem = append(rem, fn)
+ rem = append(rem, name)
}
if !pend1 || cerr == nil {
- cerr = fmt.Errorf("leveldb/storage: corrupted or incomplete %s file", fn)
+ metaFd, _ := fsParseName(name)
+ cerr = &ErrCorrupted{
+ Fd: metaFd,
+ Err: errors.New("leveldb/storage: corrupted or incomplete meta file"),
+ }
}
- } else if f != nil && f1.Num() < f.Num() {
- fs.log(fmt.Sprintf("skipping %s: obsolete", fn))
+ } else if pend1 && pendNum != fd1.Num {
+ fs.log(fmt.Sprintf("skipping %s: inconsistent pending-file num: %d vs %d", name, pendNum, fd1.Num))
+ rem = append(rem, name)
+ } else if fd1.Num < fd.Num {
+ fs.log(fmt.Sprintf("skipping %s: obsolete", name))
if pend1 {
- rem = append(rem, fn)
+ rem = append(rem, name)
}
} else {
- f = f1
+ fd = fd1
pend = pend1
}
if err := r.Close(); err != nil {
- fs.log(fmt.Sprintf("close %s: %v", fn, err))
+ fs.log(fmt.Sprintf("close %s: %v", name, err))
}
}
}
// Don't remove any files if there is no valid CURRENT file.
- if f == nil {
+ if fd.Nil() {
if cerr != nil {
err = cerr
} else {
@@ -268,267 +331,253 @@ func (fs *fileStorage) GetManifest() (f File, err error) {
}
return
}
- // Rename pending CURRENT file to an effective CURRENT.
- if pend {
- path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), f.Num())
- if err := rename(path, filepath.Join(fs.path, "CURRENT")); err != nil {
- fs.log(fmt.Sprintf("CURRENT.%d -> CURRENT: %v", f.Num(), err))
+ if !fs.readOnly {
+ // Rename pending CURRENT file to an effective CURRENT.
+ if pend {
+ path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
+ if err := rename(path, filepath.Join(fs.path, "CURRENT")); err != nil {
+ fs.log(fmt.Sprintf("CURRENT.%d -> CURRENT: %v", fd.Num, err))
+ }
}
- }
- // Remove obsolete or incomplete pending CURRENT files.
- for _, fn := range rem {
- path := filepath.Join(fs.path, fn)
- if err := os.Remove(path); err != nil {
- fs.log(fmt.Sprintf("remove %s: %v", fn, err))
+ // Remove obsolete or incomplete pending CURRENT files.
+ for _, name := range rem {
+ path := filepath.Join(fs.path, name)
+ if err := os.Remove(path); err != nil {
+ fs.log(fmt.Sprintf("remove %s: %v", name, err))
+ }
}
}
return
}
-func (fs *fileStorage) SetManifest(f File) (err error) {
+func (fs *fileStorage) List(ft FileType) (fds []FileDesc, err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
- return ErrClosed
- }
- f2, ok := f.(*file)
- if !ok || f2.t != TypeManifest {
- return ErrInvalidFile
+ return nil, ErrClosed
}
- defer func() {
- if err != nil {
- fs.log(fmt.Sprintf("CURRENT: %v", err))
- }
- }()
- path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), f2.Num())
- w, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ dir, err := os.Open(fs.path)
if err != nil {
- return err
+ return
}
- _, err = fmt.Fprintln(w, f2.name())
- // Close the file first.
- if err := w.Close(); err != nil {
- fs.log(fmt.Sprintf("close CURRENT.%d: %v", f2.num, err))
+ names, err := dir.Readdirnames(0)
+ // Close the dir first before checking for Readdirnames error.
+ if cerr := dir.Close(); cerr != nil {
+ fs.log(fmt.Sprintf("close dir: %v", cerr))
}
- if err != nil {
- return err
+ if err == nil {
+ for _, name := range names {
+ if fd, ok := fsParseName(name); ok && fd.Type&ft != 0 {
+ fds = append(fds, fd)
+ }
+ }
}
- return rename(path, filepath.Join(fs.path, "CURRENT"))
+ return
}
-func (fs *fileStorage) Close() error {
+func (fs *fileStorage) Open(fd FileDesc) (Reader, error) {
+ if !FileDescOk(fd) {
+ return nil, ErrInvalidFile
+ }
+
fs.mu.Lock()
defer fs.mu.Unlock()
if fs.open < 0 {
- return ErrClosed
- }
- // Clear the finalizer.
- runtime.SetFinalizer(fs, nil)
-
- if fs.open > 0 {
- fs.log(fmt.Sprintf("refuse to close, %d files still open", fs.open))
- return fmt.Errorf("leveldb/storage: cannot close, %d files still open", fs.open)
+ return nil, ErrClosed
}
- fs.open = -1
- e1 := fs.logw.Close()
- err := fs.flock.release()
- if err == nil {
- err = e1
+ of, err := os.OpenFile(filepath.Join(fs.path, fsGenName(fd)), os.O_RDONLY, 0)
+ if err != nil {
+ if fsHasOldName(fd) && os.IsNotExist(err) {
+ of, err = os.OpenFile(filepath.Join(fs.path, fsGenOldName(fd)), os.O_RDONLY, 0)
+ if err == nil {
+ goto ok
+ }
+ }
+ return nil, err
}
- return err
-}
-
-type fileWrap struct {
- *os.File
- f *file
+ok:
+ fs.open++
+ return &fileWrap{File: of, fs: fs, fd: fd}, nil
}
-func (fw fileWrap) Sync() error {
- if err := fw.File.Sync(); err != nil {
- return err
+func (fs *fileStorage) Create(fd FileDesc) (Writer, error) {
+ if !FileDescOk(fd) {
+ return nil, ErrInvalidFile
}
- if fw.f.Type() == TypeManifest {
- // Also sync parent directory if file type is manifest.
- // See: https://code.google.com/p/leveldb/issues/detail?id=190.
- if err := syncDir(fw.f.fs.path); err != nil {
- return err
- }
+ if fs.readOnly {
+ return nil, errReadOnly
}
- return nil
-}
-func (fw fileWrap) Close() error {
- f := fw.f
- f.fs.mu.Lock()
- defer f.fs.mu.Unlock()
- if !f.open {
- return ErrClosed
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ if fs.open < 0 {
+ return nil, ErrClosed
}
- f.open = false
- f.fs.open--
- err := fw.File.Close()
+ of, err := os.OpenFile(filepath.Join(fs.path, fsGenName(fd)), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
- f.fs.log(fmt.Sprintf("close %s.%d: %v", f.Type(), f.Num(), err))
+ return nil, err
}
- return err
+ fs.open++
+ return &fileWrap{File: of, fs: fs, fd: fd}, nil
}
-type file struct {
- fs *fileStorage
- num uint64
- t FileType
- open bool
-}
-
-func (f *file) Open() (Reader, error) {
- f.fs.mu.Lock()
- defer f.fs.mu.Unlock()
- if f.fs.open < 0 {
- return nil, ErrClosed
+func (fs *fileStorage) Remove(fd FileDesc) error {
+ if !FileDescOk(fd) {
+ return ErrInvalidFile
}
- if f.open {
- return nil, errFileOpen
+ if fs.readOnly {
+ return errReadOnly
}
- of, err := os.OpenFile(f.path(), os.O_RDONLY, 0)
+
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ if fs.open < 0 {
+ return ErrClosed
+ }
+ err := os.Remove(filepath.Join(fs.path, fsGenName(fd)))
if err != nil {
- if f.hasOldName() && os.IsNotExist(err) {
- of, err = os.OpenFile(f.oldPath(), os.O_RDONLY, 0)
- if err == nil {
- goto ok
+ if fsHasOldName(fd) && os.IsNotExist(err) {
+ if e1 := os.Remove(filepath.Join(fs.path, fsGenOldName(fd))); !os.IsNotExist(e1) {
+ fs.log(fmt.Sprintf("remove %s: %v (old name)", fd, err))
+ err = e1
}
+ } else {
+ fs.log(fmt.Sprintf("remove %s: %v", fd, err))
}
- return nil, err
}
-ok:
- f.open = true
- f.fs.open++
- return fileWrap{of, f}, nil
+ return err
}
-func (f *file) Create() (Writer, error) {
- f.fs.mu.Lock()
- defer f.fs.mu.Unlock()
- if f.fs.open < 0 {
- return nil, ErrClosed
+func (fs *fileStorage) Rename(oldfd, newfd FileDesc) error {
+ if !FileDescOk(oldfd) || !FileDescOk(newfd) {
+ return ErrInvalidFile
}
- if f.open {
- return nil, errFileOpen
+ if oldfd == newfd {
+ return nil
}
- of, err := os.OpenFile(f.path(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
- if err != nil {
- return nil, err
+ if fs.readOnly {
+ return errReadOnly
}
- f.open = true
- f.fs.open++
- return fileWrap{of, f}, nil
+
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ if fs.open < 0 {
+ return ErrClosed
+ }
+ return rename(filepath.Join(fs.path, fsGenName(oldfd)), filepath.Join(fs.path, fsGenName(newfd)))
}
-func (f *file) Replace(newfile File) error {
- f.fs.mu.Lock()
- defer f.fs.mu.Unlock()
- if f.fs.open < 0 {
+func (fs *fileStorage) Close() error {
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+ if fs.open < 0 {
return ErrClosed
}
- newfile2, ok := newfile.(*file)
- if !ok {
- return ErrInvalidFile
+ // Clear the finalizer.
+ runtime.SetFinalizer(fs, nil)
+
+ if fs.open > 0 {
+ fs.log(fmt.Sprintf("close: warning, %d files still open", fs.open))
}
- if f.open || newfile2.open {
- return errFileOpen
+ fs.open = -1
+ if fs.logw != nil {
+ fs.logw.Close()
}
- return rename(newfile2.path(), f.path())
+ return fs.flock.release()
}
-func (f *file) Type() FileType {
- return f.t
+type fileWrap struct {
+ *os.File
+ fs *fileStorage
+ fd FileDesc
+ closed bool
}
-func (f *file) Num() uint64 {
- return f.num
+func (fw *fileWrap) Sync() error {
+ if err := fw.File.Sync(); err != nil {
+ return err
+ }
+ if fw.fd.Type == TypeManifest {
+ // Also sync parent directory if file type is manifest.
+ // See: https://code.google.com/p/leveldb/issues/detail?id=190.
+ if err := syncDir(fw.fs.path); err != nil {
+ fw.fs.log(fmt.Sprintf("syncDir: %v", err))
+ return err
+ }
+ }
+ return nil
}
-func (f *file) Remove() error {
- f.fs.mu.Lock()
- defer f.fs.mu.Unlock()
- if f.fs.open < 0 {
+func (fw *fileWrap) Close() error {
+ fw.fs.mu.Lock()
+ defer fw.fs.mu.Unlock()
+ if fw.closed {
return ErrClosed
}
- if f.open {
- return errFileOpen
- }
- err := os.Remove(f.path())
+ fw.closed = true
+ fw.fs.open--
+ err := fw.File.Close()
if err != nil {
- f.fs.log(fmt.Sprintf("remove %s.%d: %v", f.Type(), f.Num(), err))
- }
- // Also try remove file with old name, just in case.
- if f.hasOldName() {
- if e1 := os.Remove(f.oldPath()); !os.IsNotExist(e1) {
- f.fs.log(fmt.Sprintf("remove %s.%d: %v (old name)", f.Type(), f.Num(), err))
- err = e1
- }
+ fw.fs.log(fmt.Sprintf("close %s: %v", fw.fd, err))
}
return err
}
-func (f *file) hasOldName() bool {
- return f.t == TypeTable
-}
-
-func (f *file) oldName() string {
- switch f.t {
- case TypeTable:
- return fmt.Sprintf("%06d.sst", f.num)
- }
- return f.name()
-}
-
-func (f *file) oldPath() string {
- return filepath.Join(f.fs.path, f.oldName())
-}
-
-func (f *file) name() string {
- switch f.t {
+func fsGenName(fd FileDesc) string {
+ switch fd.Type {
case TypeManifest:
- return fmt.Sprintf("MANIFEST-%06d", f.num)
+ return fmt.Sprintf("MANIFEST-%06d", fd.Num)
case TypeJournal:
- return fmt.Sprintf("%06d.log", f.num)
+ return fmt.Sprintf("%06d.log", fd.Num)
case TypeTable:
- return fmt.Sprintf("%06d.ldb", f.num)
+ return fmt.Sprintf("%06d.ldb", fd.Num)
case TypeTemp:
- return fmt.Sprintf("%06d.tmp", f.num)
+ return fmt.Sprintf("%06d.tmp", fd.Num)
default:
panic("invalid file type")
}
}
-func (f *file) path() string {
- return filepath.Join(f.fs.path, f.name())
+func fsHasOldName(fd FileDesc) bool {
+ return fd.Type == TypeTable
}
-func (f *file) parse(name string) bool {
- var num uint64
+func fsGenOldName(fd FileDesc) string {
+ switch fd.Type {
+ case TypeTable:
+ return fmt.Sprintf("%06d.sst", fd.Num)
+ }
+ return fsGenName(fd)
+}
+
+func fsParseName(name string) (fd FileDesc, ok bool) {
var tail string
- _, err := fmt.Sscanf(name, "%d.%s", &num, &tail)
+ _, err := fmt.Sscanf(name, "%d.%s", &fd.Num, &tail)
if err == nil {
switch tail {
case "log":
- f.t = TypeJournal
+ fd.Type = TypeJournal
case "ldb", "sst":
- f.t = TypeTable
+ fd.Type = TypeTable
case "tmp":
- f.t = TypeTemp
+ fd.Type = TypeTemp
default:
- return false
+ return
}
- f.num = num
- return true
+ return fd, true
}
- n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &num, &tail)
+ n, _ := fmt.Sscanf(name, "MANIFEST-%d%s", &fd.Num, &tail)
if n == 1 {
- f.t = TypeManifest
- f.num = num
- return true
+ fd.Type = TypeManifest
+ return fd, true
}
+ return
+}
- return false
+func fsParseNamePtr(name string, fd *FileDesc) bool {
+ _fd, ok := fsParseName(name)
+ if fd != nil {
+ *fd = _fd
+ }
+ return ok
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go
index 42940d769..bab62bfce 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_plan9.go
@@ -19,8 +19,21 @@ func (fl *plan9FileLock) release() error {
return fl.f.Close()
}
-func newFileLock(path string) (fl fileLock, err error) {
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
+ var (
+ flag int
+ perm os.FileMode
+ )
+ if readOnly {
+ flag = os.O_RDONLY
+ } else {
+ flag = os.O_RDWR
+ perm = os.ModeExclusive
+ }
+ f, err := os.OpenFile(path, flag, perm)
+ if os.IsNotExist(err) {
+ f, err = os.OpenFile(path, flag|os.O_CREATE, perm|0644)
+ }
if err != nil {
return
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go
index 102031bfd..79901ee4a 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_solaris.go
@@ -18,18 +18,27 @@ type unixFileLock struct {
}
func (fl *unixFileLock) release() error {
- if err := setFileLock(fl.f, false); err != nil {
+ if err := setFileLock(fl.f, false, false); err != nil {
return err
}
return fl.f.Close()
}
-func newFileLock(path string) (fl fileLock, err error) {
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
+ var flag int
+ if readOnly {
+ flag = os.O_RDONLY
+ } else {
+ flag = os.O_RDWR
+ }
+ f, err := os.OpenFile(path, flag, 0)
+ if os.IsNotExist(err) {
+ f, err = os.OpenFile(path, flag|os.O_CREATE, 0644)
+ }
if err != nil {
return
}
- err = setFileLock(f, true)
+ err = setFileLock(f, readOnly, true)
if err != nil {
f.Close()
return
@@ -38,7 +47,7 @@ func newFileLock(path string) (fl fileLock, err error) {
return
}
-func setFileLock(f *os.File, lock bool) error {
+func setFileLock(f *os.File, readOnly, lock bool) error {
flock := syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: 0,
@@ -46,7 +55,11 @@ func setFileLock(f *os.File, lock bool) error {
Whence: 1,
}
if lock {
- flock.Type = syscall.F_WRLCK
+ if readOnly {
+ flock.Type = syscall.F_RDLCK
+ } else {
+ flock.Type = syscall.F_WRLCK
+ }
}
return syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &flock)
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_test.go
deleted file mode 100644
index 92abcbb7d..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_test.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package storage
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "testing"
-)
-
-var cases = []struct {
- oldName []string
- name string
- ftype FileType
- num uint64
-}{
- {nil, "000100.log", TypeJournal, 100},
- {nil, "000000.log", TypeJournal, 0},
- {[]string{"000000.sst"}, "000000.ldb", TypeTable, 0},
- {nil, "MANIFEST-000002", TypeManifest, 2},
- {nil, "MANIFEST-000007", TypeManifest, 7},
- {nil, "18446744073709551615.log", TypeJournal, 18446744073709551615},
- {nil, "000100.tmp", TypeTemp, 100},
-}
-
-var invalidCases = []string{
- "",
- "foo",
- "foo-dx-100.log",
- ".log",
- "",
- "manifest",
- "CURREN",
- "CURRENTX",
- "MANIFES",
- "MANIFEST",
- "MANIFEST-",
- "XMANIFEST-3",
- "MANIFEST-3x",
- "LOC",
- "LOCKx",
- "LO",
- "LOGx",
- "18446744073709551616.log",
- "184467440737095516150.log",
- "100",
- "100.",
- "100.lop",
-}
-
-func TestFileStorage_CreateFileName(t *testing.T) {
- for _, c := range cases {
- f := &file{num: c.num, t: c.ftype}
- if f.name() != c.name {
- t.Errorf("invalid filename got '%s', want '%s'", f.name(), c.name)
- }
- }
-}
-
-func TestFileStorage_ParseFileName(t *testing.T) {
- for _, c := range cases {
- for _, name := range append([]string{c.name}, c.oldName...) {
- f := new(file)
- if !f.parse(name) {
- t.Errorf("cannot parse filename '%s'", name)
- continue
- }
- if f.Type() != c.ftype {
- t.Errorf("filename '%s' invalid type got '%d', want '%d'", name, f.Type(), c.ftype)
- }
- if f.Num() != c.num {
- t.Errorf("filename '%s' invalid number got '%d', want '%d'", name, f.Num(), c.num)
- }
- }
- }
-}
-
-func TestFileStorage_InvalidFileName(t *testing.T) {
- for _, name := range invalidCases {
- f := new(file)
- if f.parse(name) {
- t.Errorf("filename '%s' should be invalid", name)
- }
- }
-}
-
-func TestFileStorage_Locking(t *testing.T) {
- path := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldbtestfd-%d", os.Getuid()))
-
- _, err := os.Stat(path)
- if err == nil {
- err = os.RemoveAll(path)
- if err != nil {
- t.Fatal("RemoveAll: got error: ", err)
- }
- }
-
- p1, err := OpenFile(path)
- if err != nil {
- t.Fatal("OpenFile(1): got error: ", err)
- }
-
- defer os.RemoveAll(path)
-
- p2, err := OpenFile(path)
- if err != nil {
- t.Logf("OpenFile(2): got error: %s (expected)", err)
- } else {
- p2.Close()
- p1.Close()
- t.Fatal("OpenFile(2): expect error")
- }
-
- p1.Close()
-
- p3, err := OpenFile(path)
- if err != nil {
- t.Fatal("OpenFile(3): got error: ", err)
- }
- defer p3.Close()
-
- l, err := p3.Lock()
- if err != nil {
- t.Fatal("storage lock failed(1): ", err)
- }
- _, err = p3.Lock()
- if err == nil {
- t.Fatal("expect error for second storage lock attempt")
- } else {
- t.Logf("storage lock got error: %s (expected)", err)
- }
- l.Release()
- _, err = p3.Lock()
- if err != nil {
- t.Fatal("storage lock failed(2): ", err)
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go
index d0a604b7a..7e2991537 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_unix.go
@@ -18,18 +18,27 @@ type unixFileLock struct {
}
func (fl *unixFileLock) release() error {
- if err := setFileLock(fl.f, false); err != nil {
+ if err := setFileLock(fl.f, false, false); err != nil {
return err
}
return fl.f.Close()
}
-func newFileLock(path string) (fl fileLock, err error) {
- f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
+ var flag int
+ if readOnly {
+ flag = os.O_RDONLY
+ } else {
+ flag = os.O_RDWR
+ }
+ f, err := os.OpenFile(path, flag, 0)
+ if os.IsNotExist(err) {
+ f, err = os.OpenFile(path, flag|os.O_CREATE, 0644)
+ }
if err != nil {
return
}
- err = setFileLock(f, true)
+ err = setFileLock(f, readOnly, true)
if err != nil {
f.Close()
return
@@ -38,10 +47,14 @@ func newFileLock(path string) (fl fileLock, err error) {
return
}
-func setFileLock(f *os.File, lock bool) error {
+func setFileLock(f *os.File, readOnly, lock bool) error {
how := syscall.LOCK_UN
if lock {
- how = syscall.LOCK_EX
+ if readOnly {
+ how = syscall.LOCK_SH
+ } else {
+ how = syscall.LOCK_EX
+ }
}
return syscall.Flock(int(f.Fd()), how|syscall.LOCK_NB)
}
@@ -50,13 +63,23 @@ func rename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
+func isErrInvalid(err error) bool {
+ if err == os.ErrInvalid {
+ return true
+ }
+ if syserr, ok := err.(*os.SyscallError); ok && syserr.Err == syscall.EINVAL {
+ return true
+ }
+ return false
+}
+
func syncDir(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
- if err := f.Sync(); err != nil {
+ if err := f.Sync(); err != nil && !isErrInvalid(err) {
return err
}
return nil
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go
index 50c3c454e..899335fd7 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/file_storage_windows.go
@@ -29,12 +29,22 @@ func (fl *windowsFileLock) release() error {
return syscall.Close(fl.fd)
}
-func newFileLock(path string) (fl fileLock, err error) {
+func newFileLock(path string, readOnly bool) (fl fileLock, err error) {
pathp, err := syscall.UTF16PtrFromString(path)
if err != nil {
return
}
- fd, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.CREATE_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ var access, shareMode uint32
+ if readOnly {
+ access = syscall.GENERIC_READ
+ shareMode = syscall.FILE_SHARE_READ
+ } else {
+ access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
+ }
+ fd, err := syscall.CreateFile(pathp, access, shareMode, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ if err == syscall.ERROR_FILE_NOT_FOUND {
+ fd, err = syscall.CreateFile(pathp, access, shareMode, nil, syscall.OPEN_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
+ }
if err != nil {
return
}
@@ -47,9 +57,8 @@ func moveFileEx(from *uint16, to *uint16, flags uint32) error {
if r1 == 0 {
if e1 != 0 {
return error(e1)
- } else {
- return syscall.EINVAL
}
+ return syscall.EINVAL
}
return nil
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go
index fc1c8165d..9b70e1513 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage.go
@@ -10,8 +10,6 @@ import (
"bytes"
"os"
"sync"
-
- "github.com/syndtr/goleveldb/leveldb/util"
)
const typeShift = 3
@@ -32,10 +30,10 @@ func (lock *memStorageLock) Release() {
// memStorage is a memory-backed storage.
type memStorage struct {
- mu sync.Mutex
- slock *memStorageLock
- files map[uint64]*memFile
- manifest *memFilePtr
+ mu sync.Mutex
+ slock *memStorageLock
+ files map[uint64]*memFile
+ meta FileDesc
}
// NewMemStorage returns a new memory-backed storage implementation.
@@ -45,7 +43,7 @@ func NewMemStorage() Storage {
}
}
-func (ms *memStorage) Lock() (util.Releaser, error) {
+func (ms *memStorage) Lock() (Lock, error) {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.slock != nil {
@@ -57,147 +55,164 @@ func (ms *memStorage) Lock() (util.Releaser, error) {
func (*memStorage) Log(str string) {}
-func (ms *memStorage) GetFile(num uint64, t FileType) File {
- return &memFilePtr{ms: ms, num: num, t: t}
-}
+func (ms *memStorage) SetMeta(fd FileDesc) error {
+ if !FileDescOk(fd) {
+ return ErrInvalidFile
+ }
-func (ms *memStorage) GetFiles(t FileType) ([]File, error) {
ms.mu.Lock()
- var ff []File
- for x, _ := range ms.files {
- num, mt := x>>typeShift, FileType(x)&TypeAll
- if mt&t == 0 {
- continue
- }
- ff = append(ff, &memFilePtr{ms: ms, num: num, t: mt})
- }
+ ms.meta = fd
ms.mu.Unlock()
- return ff, nil
+ return nil
}
-func (ms *memStorage) GetManifest() (File, error) {
+func (ms *memStorage) GetMeta() (FileDesc, error) {
ms.mu.Lock()
defer ms.mu.Unlock()
- if ms.manifest == nil {
- return nil, os.ErrNotExist
+ if ms.meta.Nil() {
+ return FileDesc{}, os.ErrNotExist
}
- return ms.manifest, nil
+ return ms.meta, nil
}
-func (ms *memStorage) SetManifest(f File) error {
- fm, ok := f.(*memFilePtr)
- if !ok || fm.t != TypeManifest {
- return ErrInvalidFile
- }
+func (ms *memStorage) List(ft FileType) ([]FileDesc, error) {
ms.mu.Lock()
- ms.manifest = fm
+ var fds []FileDesc
+ for x, _ := range ms.files {
+ fd := unpackFile(x)
+ if fd.Type&ft != 0 {
+ fds = append(fds, fd)
+ }
+ }
ms.mu.Unlock()
- return nil
+ return fds, nil
}
-func (*memStorage) Close() error { return nil }
-
-type memReader struct {
- *bytes.Reader
- m *memFile
-}
-
-func (mr *memReader) Close() error {
- return mr.m.Close()
-}
-
-type memFile struct {
- bytes.Buffer
- ms *memStorage
- open bool
-}
-
-func (*memFile) Sync() error { return nil }
-func (m *memFile) Close() error {
- m.ms.mu.Lock()
- m.open = false
- m.ms.mu.Unlock()
- return nil
-}
-
-type memFilePtr struct {
- ms *memStorage
- num uint64
- t FileType
-}
-
-func (p *memFilePtr) x() uint64 {
- return p.Num()<<typeShift | uint64(p.Type())
-}
+func (ms *memStorage) Open(fd FileDesc) (Reader, error) {
+ if !FileDescOk(fd) {
+ return nil, ErrInvalidFile
+ }
-func (p *memFilePtr) Open() (Reader, error) {
- ms := p.ms
ms.mu.Lock()
defer ms.mu.Unlock()
- if m, exist := ms.files[p.x()]; exist {
+ if m, exist := ms.files[packFile(fd)]; exist {
if m.open {
return nil, errFileOpen
}
m.open = true
- return &memReader{Reader: bytes.NewReader(m.Bytes()), m: m}, nil
+ return &memReader{Reader: bytes.NewReader(m.Bytes()), ms: ms, m: m}, nil
}
return nil, os.ErrNotExist
}
-func (p *memFilePtr) Create() (Writer, error) {
- ms := p.ms
+func (ms *memStorage) Create(fd FileDesc) (Writer, error) {
+ if !FileDescOk(fd) {
+ return nil, ErrInvalidFile
+ }
+
+ x := packFile(fd)
ms.mu.Lock()
defer ms.mu.Unlock()
- m, exist := ms.files[p.x()]
+ m, exist := ms.files[x]
if exist {
if m.open {
return nil, errFileOpen
}
m.Reset()
} else {
- m = &memFile{ms: ms}
- ms.files[p.x()] = m
+ m = &memFile{}
+ ms.files[x] = m
}
m.open = true
- return m, nil
+ return &memWriter{memFile: m, ms: ms}, nil
}
-func (p *memFilePtr) Replace(newfile File) error {
- p1, ok := newfile.(*memFilePtr)
- if !ok {
+func (ms *memStorage) Remove(fd FileDesc) error {
+ if !FileDescOk(fd) {
return ErrInvalidFile
}
- ms := p.ms
+
+ x := packFile(fd)
ms.mu.Lock()
defer ms.mu.Unlock()
- m1, exist := ms.files[p1.x()]
+ if _, exist := ms.files[x]; exist {
+ delete(ms.files, x)
+ return nil
+ }
+ return os.ErrNotExist
+}
+
+func (ms *memStorage) Rename(oldfd, newfd FileDesc) error {
+ if FileDescOk(oldfd) || FileDescOk(newfd) {
+ return ErrInvalidFile
+ }
+ if oldfd == newfd {
+ return nil
+ }
+
+ oldx := packFile(oldfd)
+ newx := packFile(newfd)
+ ms.mu.Lock()
+ defer ms.mu.Unlock()
+ oldm, exist := ms.files[oldx]
if !exist {
return os.ErrNotExist
}
- m0, exist := ms.files[p.x()]
- if (exist && m0.open) || m1.open {
+ newm, exist := ms.files[newx]
+ if (exist && newm.open) || oldm.open {
return errFileOpen
}
- delete(ms.files, p1.x())
- ms.files[p.x()] = m1
+ delete(ms.files, oldx)
+ ms.files[newx] = oldm
return nil
}
-func (p *memFilePtr) Type() FileType {
- return p.t
+func (*memStorage) Close() error { return nil }
+
+type memFile struct {
+ bytes.Buffer
+ open bool
}
-func (p *memFilePtr) Num() uint64 {
- return p.num
+type memReader struct {
+ *bytes.Reader
+ ms *memStorage
+ m *memFile
+ closed bool
}
-func (p *memFilePtr) Remove() error {
- ms := p.ms
- ms.mu.Lock()
- defer ms.mu.Unlock()
- if _, exist := ms.files[p.x()]; exist {
- delete(ms.files, p.x())
- return nil
+func (mr *memReader) Close() error {
+ mr.ms.mu.Lock()
+ defer mr.ms.mu.Unlock()
+ if mr.closed {
+ return ErrClosed
}
- return os.ErrNotExist
+ mr.m.open = false
+ return nil
+}
+
+type memWriter struct {
+ *memFile
+ ms *memStorage
+ closed bool
+}
+
+func (*memWriter) Sync() error { return nil }
+
+func (mw *memWriter) Close() error {
+ mw.ms.mu.Lock()
+ defer mw.ms.mu.Unlock()
+ if mw.closed {
+ return ErrClosed
+ }
+ mw.memFile.open = false
+ return nil
+}
+
+func packFile(fd FileDesc) uint64 {
+ return uint64(fd.Num)<<typeShift | uint64(fd.Type)
+}
+
+func unpackFile(x uint64) FileDesc {
+ return FileDesc{FileType(x) & TypeAll, int64(x >> typeShift)}
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage_test.go
deleted file mode 100644
index 23bb074b4..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/mem_storage_test.go
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2013, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package storage
-
-import (
- "bytes"
- "testing"
-)
-
-func TestMemStorage(t *testing.T) {
- m := NewMemStorage()
-
- l, err := m.Lock()
- if err != nil {
- t.Fatal("storage lock failed(1): ", err)
- }
- _, err = m.Lock()
- if err == nil {
- t.Fatal("expect error for second storage lock attempt")
- } else {
- t.Logf("storage lock got error: %s (expected)", err)
- }
- l.Release()
- _, err = m.Lock()
- if err != nil {
- t.Fatal("storage lock failed(2): ", err)
- }
-
- f := m.GetFile(1, TypeTable)
- if f.Num() != 1 && f.Type() != TypeTable {
- t.Fatal("invalid file number and type")
- }
- w, _ := f.Create()
- w.Write([]byte("abc"))
- w.Close()
- if ff, _ := m.GetFiles(TypeAll); len(ff) != 1 {
- t.Fatal("invalid GetFiles len")
- }
- buf := new(bytes.Buffer)
- r, err := f.Open()
- if err != nil {
- t.Fatal("Open: got error: ", err)
- }
- buf.ReadFrom(r)
- r.Close()
- if got := buf.String(); got != "abc" {
- t.Fatalf("Read: invalid value, want=abc got=%s", got)
- }
- if _, err := f.Open(); err != nil {
- t.Fatal("Open: got error: ", err)
- }
- if _, err := m.GetFile(1, TypeTable).Open(); err == nil {
- t.Fatal("expecting error")
- }
- f.Remove()
- if ff, _ := m.GetFiles(TypeAll); len(ff) != 0 {
- t.Fatal("invalid GetFiles len", len(ff))
- }
- if _, err := f.Open(); err == nil {
- t.Fatal("expecting error")
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go
index 85dd70b06..9b30b6727 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage/storage.go
@@ -15,7 +15,7 @@ import (
"github.com/syndtr/goleveldb/leveldb/util"
)
-type FileType uint32
+type FileType int
const (
TypeManifest FileType = 1 << iota
@@ -46,6 +46,22 @@ var (
ErrClosed = errors.New("leveldb/storage: closed")
)
+// ErrCorrupted is the type that wraps errors that indicate corruption of
+// a file. Package storage has its own type instead of using
+// errors.ErrCorrupted to prevent circular import.
+type ErrCorrupted struct {
+ Fd FileDesc
+ Err error
+}
+
+func (e *ErrCorrupted) Error() string {
+ if !e.Fd.Nil() {
+ return fmt.Sprintf("%v [file=%v]", e.Err, e.Fd)
+ } else {
+ return e.Err.Error()
+ }
+}
+
// Syncer is the interface that wraps basic Sync method.
type Syncer interface {
// Sync commits the current contents of the file to stable storage.
@@ -67,31 +83,47 @@ type Writer interface {
Syncer
}
-// File is the file. A file instance must be goroutine-safe.
-type File interface {
- // Open opens the file for read. Returns os.ErrNotExist error
- // if the file does not exist.
- // Returns ErrClosed if the underlying storage is closed.
- Open() (r Reader, err error)
-
- // Create creates the file for writting. Truncate the file if
- // already exist.
- // Returns ErrClosed if the underlying storage is closed.
- Create() (w Writer, err error)
+type Lock interface {
+ util.Releaser
+}
- // Replace replaces file with newfile.
- // Returns ErrClosed if the underlying storage is closed.
- Replace(newfile File) error
+// FileDesc is a file descriptor.
+type FileDesc struct {
+ Type FileType
+ Num int64
+}
- // Type returns the file type
- Type() FileType
+func (fd FileDesc) String() string {
+ switch fd.Type {
+ case TypeManifest:
+ return fmt.Sprintf("MANIFEST-%06d", fd.Num)
+ case TypeJournal:
+ return fmt.Sprintf("%06d.log", fd.Num)
+ case TypeTable:
+ return fmt.Sprintf("%06d.ldb", fd.Num)
+ case TypeTemp:
+ return fmt.Sprintf("%06d.tmp", fd.Num)
+ default:
+ return fmt.Sprintf("%#x-%d", fd.Type, fd.Num)
+ }
+}
- // Num returns the file number.
- Num() uint64
+// Nil returns true if fd == (FileDesc{}).
+func (fd FileDesc) Nil() bool {
+ return fd == (FileDesc{})
+}
- // Remove removes the file.
- // Returns ErrClosed if the underlying storage is closed.
- Remove() error
+// FileDescOk returns true if fd is a valid file descriptor.
+func FileDescOk(fd FileDesc) bool {
+ switch fd.Type {
+ case TypeManifest:
+ case TypeJournal:
+ case TypeTable:
+ case TypeTemp:
+ default:
+ return false
+ }
+ return fd.Num >= 0
}
// Storage is the storage. A storage instance must be goroutine-safe.
@@ -99,59 +131,47 @@ type Storage interface {
// Lock locks the storage. Any subsequent attempt to call Lock will fail
// until the last lock released.
// After use the caller should call the Release method.
- Lock() (l util.Releaser, err error)
+ Lock() (Lock, error)
- // Log logs a string. This is used for logging. An implementation
- // may write to a file, stdout or simply do nothing.
+ // Log logs a string. This is used for logging.
+ // An implementation may write to a file, stdout or simply do nothing.
Log(str string)
- // GetFile returns a file for the given number and type. GetFile will never
- // returns nil, even if the underlying storage is closed.
- GetFile(num uint64, t FileType) File
+ // SetMeta sets to point to the given fd, which then can be acquired using
+ // GetMeta method.
+ // SetMeta should be implemented in such way that changes should happened
+ // atomically.
+ SetMeta(fd FileDesc) error
- // GetFiles returns a slice of files that match the given file types.
- // The file types may be OR'ed together.
- GetFiles(t FileType) ([]File, error)
+ // GetManifest returns a manifest file.
+ // Returns os.ErrNotExist if meta doesn't point to any fd, or point to fd
+ // that doesn't exist.
+ GetMeta() (FileDesc, error)
- // GetManifest returns a manifest file. Returns os.ErrNotExist if manifest
- // file does not exist.
- GetManifest() (File, error)
+ // List returns fds that match the given file types.
+ // The file types may be OR'ed together.
+ List(ft FileType) ([]FileDesc, error)
- // SetManifest sets the given file as manifest file. The given file should
- // be a manifest file type or error will be returned.
- SetManifest(f File) error
+ // Open opens file with the given fd read-only.
+ // Returns os.ErrNotExist error if the file does not exist.
+ // Returns ErrClosed if the underlying storage is closed.
+ Open(fd FileDesc) (Reader, error)
- // Close closes the storage. It is valid to call Close multiple times.
- // Other methods should not be called after the storage has been closed.
- Close() error
-}
+ // Create creates file with the given fd, truncate if already exist and
+ // opens write-only.
+ // Returns ErrClosed if the underlying storage is closed.
+ Create(fd FileDesc) (Writer, error)
-// FileInfo wraps basic file info.
-type FileInfo struct {
- Type FileType
- Num uint64
-}
+ // Remove removes file with the given fd.
+ // Returns ErrClosed if the underlying storage is closed.
+ Remove(fd FileDesc) error
-func (fi FileInfo) String() string {
- switch fi.Type {
- case TypeManifest:
- return fmt.Sprintf("MANIFEST-%06d", fi.Num)
- case TypeJournal:
- return fmt.Sprintf("%06d.log", fi.Num)
- case TypeTable:
- return fmt.Sprintf("%06d.ldb", fi.Num)
- case TypeTemp:
- return fmt.Sprintf("%06d.tmp", fi.Num)
- default:
- return fmt.Sprintf("%#x-%d", fi.Type, fi.Num)
- }
-}
+ // Rename renames file from oldfd to newfd.
+ // Returns ErrClosed if the underlying storage is closed.
+ Rename(oldfd, newfd FileDesc) error
-// NewFileInfo creates new FileInfo from the given File. It will returns nil
-// if File is nil.
-func NewFileInfo(f File) *FileInfo {
- if f == nil {
- return nil
- }
- return &FileInfo{f.Type(), f.Num()}
+ // Close closes the storage.
+ // It is valid to call Close multiple times. Other methods should not be
+ // called after the storage has been closed.
+ Close() error
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage_test.go
deleted file mode 100644
index dc1f1fb54..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/storage_test.go
+++ /dev/null
@@ -1,539 +0,0 @@
-// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENE file.
-
-package leveldb
-
-import (
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math/rand"
- "os"
- "path/filepath"
- "sync"
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/storage"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-const typeShift = 4
-
-var (
- tsErrInvalidFile = errors.New("leveldb.testStorage: invalid file for argument")
- tsErrFileOpen = errors.New("leveldb.testStorage: file still open")
-)
-
-var (
- tsFSEnv = os.Getenv("GOLEVELDB_USEFS")
- tsTempdir = os.Getenv("GOLEVELDB_TEMPDIR")
- tsKeepFS = tsFSEnv == "2"
- tsFS = tsKeepFS || tsFSEnv == "" || tsFSEnv == "1"
- tsMU = &sync.Mutex{}
- tsNum = 0
-)
-
-type tsOp uint
-
-const (
- tsOpOpen tsOp = iota
- tsOpCreate
- tsOpRead
- tsOpReadAt
- tsOpWrite
- tsOpSync
-
- tsOpNum
-)
-
-type tsLock struct {
- ts *testStorage
- r util.Releaser
-}
-
-func (l tsLock) Release() {
- l.r.Release()
- l.ts.t.Log("I: storage lock released")
-}
-
-type tsReader struct {
- tf tsFile
- storage.Reader
-}
-
-func (tr tsReader) Read(b []byte) (n int, err error) {
- ts := tr.tf.ts
- ts.countRead(tr.tf.Type())
- if tr.tf.shouldErrLocked(tsOpRead) {
- return 0, errors.New("leveldb.testStorage: emulated read error")
- }
- n, err = tr.Reader.Read(b)
- if err != nil && err != io.EOF {
- ts.t.Errorf("E: read error, num=%d type=%v n=%d: %v", tr.tf.Num(), tr.tf.Type(), n, err)
- }
- return
-}
-
-func (tr tsReader) ReadAt(b []byte, off int64) (n int, err error) {
- ts := tr.tf.ts
- ts.countRead(tr.tf.Type())
- if tr.tf.shouldErrLocked(tsOpReadAt) {
- return 0, errors.New("leveldb.testStorage: emulated readAt error")
- }
- n, err = tr.Reader.ReadAt(b, off)
- if err != nil && err != io.EOF {
- ts.t.Errorf("E: readAt error, num=%d type=%v off=%d n=%d: %v", tr.tf.Num(), tr.tf.Type(), off, n, err)
- }
- return
-}
-
-func (tr tsReader) Close() (err error) {
- err = tr.Reader.Close()
- tr.tf.close("reader", err)
- return
-}
-
-type tsWriter struct {
- tf tsFile
- storage.Writer
-}
-
-func (tw tsWriter) Write(b []byte) (n int, err error) {
- if tw.tf.shouldErrLocked(tsOpWrite) {
- return 0, errors.New("leveldb.testStorage: emulated write error")
- }
- n, err = tw.Writer.Write(b)
- if err != nil {
- tw.tf.ts.t.Errorf("E: write error, num=%d type=%v n=%d: %v", tw.tf.Num(), tw.tf.Type(), n, err)
- }
- return
-}
-
-func (tw tsWriter) Sync() (err error) {
- ts := tw.tf.ts
- ts.mu.Lock()
- for ts.emuDelaySync&tw.tf.Type() != 0 {
- ts.cond.Wait()
- }
- ts.mu.Unlock()
- if tw.tf.shouldErrLocked(tsOpSync) {
- return errors.New("leveldb.testStorage: emulated sync error")
- }
- err = tw.Writer.Sync()
- if err != nil {
- tw.tf.ts.t.Errorf("E: sync error, num=%d type=%v: %v", tw.tf.Num(), tw.tf.Type(), err)
- }
- return
-}
-
-func (tw tsWriter) Close() (err error) {
- err = tw.Writer.Close()
- tw.tf.close("writer", err)
- return
-}
-
-type tsFile struct {
- ts *testStorage
- storage.File
-}
-
-func (tf tsFile) x() uint64 {
- return tf.Num()<<typeShift | uint64(tf.Type())
-}
-
-func (tf tsFile) shouldErr(op tsOp) bool {
- return tf.ts.shouldErr(tf, op)
-}
-
-func (tf tsFile) shouldErrLocked(op tsOp) bool {
- tf.ts.mu.Lock()
- defer tf.ts.mu.Unlock()
- return tf.shouldErr(op)
-}
-
-func (tf tsFile) checkOpen(m string) error {
- ts := tf.ts
- if writer, ok := ts.opens[tf.x()]; ok {
- if writer {
- ts.t.Errorf("E: cannot %s file, num=%d type=%v: a writer still open", m, tf.Num(), tf.Type())
- } else {
- ts.t.Errorf("E: cannot %s file, num=%d type=%v: a reader still open", m, tf.Num(), tf.Type())
- }
- return tsErrFileOpen
- }
- return nil
-}
-
-func (tf tsFile) close(m string, err error) {
- ts := tf.ts
- ts.mu.Lock()
- defer ts.mu.Unlock()
- if _, ok := ts.opens[tf.x()]; !ok {
- ts.t.Errorf("E: %s: redudant file closing, num=%d type=%v", m, tf.Num(), tf.Type())
- } else if err == nil {
- ts.t.Logf("I: %s: file closed, num=%d type=%v", m, tf.Num(), tf.Type())
- }
- delete(ts.opens, tf.x())
- if err != nil {
- ts.t.Errorf("E: %s: cannot close file, num=%d type=%v: %v", m, tf.Num(), tf.Type(), err)
- }
-}
-
-func (tf tsFile) Open() (r storage.Reader, err error) {
- ts := tf.ts
- ts.mu.Lock()
- defer ts.mu.Unlock()
- err = tf.checkOpen("open")
- if err != nil {
- return
- }
- if tf.shouldErr(tsOpOpen) {
- err = errors.New("leveldb.testStorage: emulated open error")
- return
- }
- r, err = tf.File.Open()
- if err != nil {
- if ts.ignoreOpenErr&tf.Type() != 0 {
- ts.t.Logf("I: cannot open file, num=%d type=%v: %v (ignored)", tf.Num(), tf.Type(), err)
- } else {
- ts.t.Errorf("E: cannot open file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
- }
- } else {
- ts.t.Logf("I: file opened, num=%d type=%v", tf.Num(), tf.Type())
- ts.opens[tf.x()] = false
- r = tsReader{tf, r}
- }
- return
-}
-
-func (tf tsFile) Create() (w storage.Writer, err error) {
- ts := tf.ts
- ts.mu.Lock()
- defer ts.mu.Unlock()
- err = tf.checkOpen("create")
- if err != nil {
- return
- }
- if tf.shouldErr(tsOpCreate) {
- err = errors.New("leveldb.testStorage: emulated create error")
- return
- }
- w, err = tf.File.Create()
- if err != nil {
- ts.t.Errorf("E: cannot create file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
- } else {
- ts.t.Logf("I: file created, num=%d type=%v", tf.Num(), tf.Type())
- ts.opens[tf.x()] = true
- w = tsWriter{tf, w}
- }
- return
-}
-
-func (tf tsFile) Replace(newfile storage.File) (err error) {
- ts := tf.ts
- ts.mu.Lock()
- defer ts.mu.Unlock()
- err = tf.checkOpen("replace")
- if err != nil {
- return
- }
- err = tf.File.Replace(newfile.(tsFile).File)
- if err != nil {
- ts.t.Errorf("E: cannot replace file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
- } else {
- ts.t.Logf("I: file replace, num=%d type=%v", tf.Num(), tf.Type())
- }
- return
-}
-
-func (tf tsFile) Remove() (err error) {
- ts := tf.ts
- ts.mu.Lock()
- defer ts.mu.Unlock()
- err = tf.checkOpen("remove")
- if err != nil {
- return
- }
- err = tf.File.Remove()
- if err != nil {
- ts.t.Errorf("E: cannot remove file, num=%d type=%v: %v", tf.Num(), tf.Type(), err)
- } else {
- ts.t.Logf("I: file removed, num=%d type=%v", tf.Num(), tf.Type())
- }
- return
-}
-
-type testStorage struct {
- t *testing.T
- storage.Storage
- closeFn func() error
-
- mu sync.Mutex
- cond sync.Cond
- // Open files, true=writer, false=reader
- opens map[uint64]bool
- emuDelaySync storage.FileType
- ignoreOpenErr storage.FileType
- readCnt uint64
- readCntEn storage.FileType
-
- emuErr [tsOpNum]storage.FileType
- emuErrOnce [tsOpNum]storage.FileType
- emuRandErr [tsOpNum]storage.FileType
- emuRandErrProb int
- emuErrOnceMap map[uint64]uint
- emuRandRand *rand.Rand
-}
-
-func (ts *testStorage) shouldErr(tf tsFile, op tsOp) bool {
- if ts.emuErr[op]&tf.Type() != 0 {
- return true
- } else if ts.emuRandErr[op]&tf.Type() != 0 || ts.emuErrOnce[op]&tf.Type() != 0 {
- sop := uint(1) << op
- eop := ts.emuErrOnceMap[tf.x()]
- if eop&sop == 0 && (ts.emuRandRand.Int()%ts.emuRandErrProb == 0 || ts.emuErrOnce[op]&tf.Type() != 0) {
- ts.emuErrOnceMap[tf.x()] = eop | sop
- ts.t.Logf("I: emulated error: file=%d type=%v op=%v", tf.Num(), tf.Type(), op)
- return true
- }
- }
- return false
-}
-
-func (ts *testStorage) SetEmuErr(t storage.FileType, ops ...tsOp) {
- ts.mu.Lock()
- for _, op := range ops {
- ts.emuErr[op] = t
- }
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) SetEmuErrOnce(t storage.FileType, ops ...tsOp) {
- ts.mu.Lock()
- for _, op := range ops {
- ts.emuErrOnce[op] = t
- }
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) SetEmuRandErr(t storage.FileType, ops ...tsOp) {
- ts.mu.Lock()
- for _, op := range ops {
- ts.emuRandErr[op] = t
- }
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) SetEmuRandErrProb(prob int) {
- ts.mu.Lock()
- ts.emuRandErrProb = prob
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) DelaySync(t storage.FileType) {
- ts.mu.Lock()
- ts.emuDelaySync |= t
- ts.cond.Broadcast()
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) ReleaseSync(t storage.FileType) {
- ts.mu.Lock()
- ts.emuDelaySync &= ^t
- ts.cond.Broadcast()
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) ReadCounter() uint64 {
- ts.mu.Lock()
- defer ts.mu.Unlock()
- return ts.readCnt
-}
-
-func (ts *testStorage) ResetReadCounter() {
- ts.mu.Lock()
- ts.readCnt = 0
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) SetReadCounter(t storage.FileType) {
- ts.mu.Lock()
- ts.readCntEn = t
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) countRead(t storage.FileType) {
- ts.mu.Lock()
- if ts.readCntEn&t != 0 {
- ts.readCnt++
- }
- ts.mu.Unlock()
-}
-
-func (ts *testStorage) SetIgnoreOpenErr(t storage.FileType) {
- ts.ignoreOpenErr = t
-}
-
-func (ts *testStorage) Lock() (r util.Releaser, err error) {
- r, err = ts.Storage.Lock()
- if err != nil {
- ts.t.Logf("W: storage locking failed: %v", err)
- } else {
- ts.t.Log("I: storage locked")
- r = tsLock{ts, r}
- }
- return
-}
-
-func (ts *testStorage) Log(str string) {
- ts.t.Log("L: " + str)
- ts.Storage.Log(str)
-}
-
-func (ts *testStorage) GetFile(num uint64, t storage.FileType) storage.File {
- return tsFile{ts, ts.Storage.GetFile(num, t)}
-}
-
-func (ts *testStorage) GetFiles(t storage.FileType) (ff []storage.File, err error) {
- ff0, err := ts.Storage.GetFiles(t)
- if err != nil {
- ts.t.Errorf("E: get files failed: %v", err)
- return
- }
- ff = make([]storage.File, len(ff0))
- for i, f := range ff0 {
- ff[i] = tsFile{ts, f}
- }
- ts.t.Logf("I: get files, type=0x%x count=%d", int(t), len(ff))
- return
-}
-
-func (ts *testStorage) GetManifest() (f storage.File, err error) {
- f0, err := ts.Storage.GetManifest()
- if err != nil {
- if !os.IsNotExist(err) {
- ts.t.Errorf("E: get manifest failed: %v", err)
- }
- return
- }
- f = tsFile{ts, f0}
- ts.t.Logf("I: get manifest, num=%d", f.Num())
- return
-}
-
-func (ts *testStorage) SetManifest(f storage.File) error {
- tf, ok := f.(tsFile)
- if !ok {
- ts.t.Error("E: set manifest failed: type assertion failed")
- return tsErrInvalidFile
- } else if tf.Type() != storage.TypeManifest {
- ts.t.Errorf("E: set manifest failed: invalid file type: %s", tf.Type())
- return tsErrInvalidFile
- }
- err := ts.Storage.SetManifest(tf.File)
- if err != nil {
- ts.t.Errorf("E: set manifest failed: %v", err)
- } else {
- ts.t.Logf("I: set manifest, num=%d", tf.Num())
- }
- return err
-}
-
-func (ts *testStorage) Close() error {
- ts.CloseCheck()
- err := ts.Storage.Close()
- if err != nil {
- ts.t.Errorf("E: closing storage failed: %v", err)
- } else {
- ts.t.Log("I: storage closed")
- }
- if ts.closeFn != nil {
- if err := ts.closeFn(); err != nil {
- ts.t.Errorf("E: close function: %v", err)
- }
- }
- return err
-}
-
-func (ts *testStorage) CloseCheck() {
- ts.mu.Lock()
- if len(ts.opens) == 0 {
- ts.t.Log("I: all files are closed")
- } else {
- ts.t.Errorf("E: %d files still open", len(ts.opens))
- for x, writer := range ts.opens {
- num, tt := x>>typeShift, storage.FileType(x)&storage.TypeAll
- ts.t.Errorf("E: * num=%d type=%v writer=%v", num, tt, writer)
- }
- }
- ts.mu.Unlock()
-}
-
-func newTestStorage(t *testing.T) *testStorage {
- var stor storage.Storage
- var closeFn func() error
- if tsFS {
- for {
- tsMU.Lock()
- num := tsNum
- tsNum++
- tsMU.Unlock()
- tempdir := tsTempdir
- if tempdir == "" {
- tempdir = os.TempDir()
- }
- path := filepath.Join(tempdir, fmt.Sprintf("goleveldb-test%d0%d0%d", os.Getuid(), os.Getpid(), num))
- if _, err := os.Stat(path); err != nil {
- stor, err = storage.OpenFile(path)
- if err != nil {
- t.Fatalf("F: cannot create storage: %v", err)
- }
- t.Logf("I: storage created: %s", path)
- closeFn = func() error {
- for _, name := range []string{"LOG.old", "LOG"} {
- f, err := os.Open(filepath.Join(path, name))
- if err != nil {
- continue
- }
- if log, err := ioutil.ReadAll(f); err != nil {
- t.Logf("---------------------- %s ----------------------", name)
- t.Logf("cannot read log: %v", err)
- t.Logf("---------------------- %s ----------------------", name)
- } else if len(log) > 0 {
- t.Logf("---------------------- %s ----------------------\n%s", name, string(log))
- t.Logf("---------------------- %s ----------------------", name)
- }
- f.Close()
- }
- if t.Failed() {
- t.Logf("testing failed, test DB preserved at %s", path)
- return nil
- }
- if tsKeepFS {
- return nil
- }
- return os.RemoveAll(path)
- }
-
- break
- }
- }
- } else {
- stor = storage.NewMemStorage()
- }
- ts := &testStorage{
- t: t,
- Storage: stor,
- closeFn: closeFn,
- opens: make(map[uint64]bool),
- emuErrOnceMap: make(map[uint64]uint),
- emuRandErrProb: 0x999,
- emuRandRand: rand.New(rand.NewSource(0xfacedead)),
- }
- ts.cond.L = &ts.mu
- return ts
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table.go
index 3e8df6af5..7030b22ef 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table.go
@@ -21,9 +21,9 @@ import (
// tFile holds basic information about a table.
type tFile struct {
- file storage.File
+ fd storage.FileDesc
seekLeft int32
- size uint64
+ size int64
imin, imax iKey
}
@@ -48,9 +48,9 @@ func (t *tFile) consumeSeek() int32 {
}
// Creates new tFile.
-func newTableFile(file storage.File, size uint64, imin, imax iKey) *tFile {
+func newTableFile(fd storage.FileDesc, size int64, imin, imax iKey) *tFile {
f := &tFile{
- file: file,
+ fd: fd,
size: size,
imin: imin,
imax: imax,
@@ -77,6 +77,10 @@ func newTableFile(file storage.File, size uint64, imin, imax iKey) *tFile {
return f
}
+func tableFileFromRecord(r atRecord) *tFile {
+ return newTableFile(storage.FileDesc{storage.TypeTable, r.num}, r.size, r.imin, r.imax)
+}
+
// tFiles hold multiple tFile.
type tFiles []*tFile
@@ -89,7 +93,7 @@ func (tf tFiles) nums() string {
if i != 0 {
x += ", "
}
- x += fmt.Sprint(f.file.Num())
+ x += fmt.Sprint(f.fd.Num)
}
x += " ]"
return x
@@ -101,7 +105,7 @@ func (tf tFiles) lessByKey(icmp *iComparer, i, j int) bool {
a, b := tf[i], tf[j]
n := icmp.Compare(a.imin, b.imin)
if n == 0 {
- return a.file.Num() < b.file.Num()
+ return a.fd.Num < b.fd.Num
}
return n < 0
}
@@ -109,7 +113,7 @@ func (tf tFiles) lessByKey(icmp *iComparer, i, j int) bool {
// Returns true if i file number is greater than j.
// This used for sort by file number in descending order.
func (tf tFiles) lessByNum(i, j int) bool {
- return tf[i].file.Num() > tf[j].file.Num()
+ return tf[i].fd.Num > tf[j].fd.Num
}
// Sorts tables by key in ascending order.
@@ -123,7 +127,7 @@ func (tf tFiles) sortByNum() {
}
// Returns sum of all tables size.
-func (tf tFiles) size() (sum uint64) {
+func (tf tFiles) size() (sum int64) {
for _, t := range tf {
sum += t.size
}
@@ -162,7 +166,7 @@ func (tf tFiles) overlaps(icmp *iComparer, umin, umax []byte, unsorted bool) boo
i := 0
if len(umin) > 0 {
// Find the earliest possible internal key for min.
- i = tf.searchMax(icmp, newIkey(umin, kMaxSeq, ktSeek))
+ i = tf.searchMax(icmp, makeIkey(nil, umin, kMaxSeq, ktSeek))
}
if i >= len(tf) {
// Beginning of range is after all files, so no overlap.
@@ -287,6 +291,7 @@ func (x *tFilesSortByNum) Less(i, j int) bool {
// Table operations.
type tOps struct {
s *session
+ noSync bool
cache *cache.Cache
bcache *cache.Cache
bpool *util.BufferPool
@@ -294,16 +299,16 @@ type tOps struct {
// Creates an empty table and returns table writer.
func (t *tOps) create() (*tWriter, error) {
- file := t.s.getTableFile(t.s.allocFileNum())
- fw, err := file.Create()
+ fd := storage.FileDesc{storage.TypeTable, t.s.allocFileNum()}
+ fw, err := t.s.stor.Create(fd)
if err != nil {
return nil, err
}
return &tWriter{
- t: t,
- file: file,
- w: fw,
- tw: table.NewWriter(fw, t.s.o.Options),
+ t: t,
+ fd: fd,
+ w: fw,
+ tw: table.NewWriter(fw, t.s.o.Options),
}, nil
}
@@ -339,21 +344,20 @@ func (t *tOps) createFrom(src iterator.Iterator) (f *tFile, n int, err error) {
// Opens table. It returns a cache handle, which should
// be released after use.
func (t *tOps) open(f *tFile) (ch *cache.Handle, err error) {
- num := f.file.Num()
- ch = t.cache.Get(0, num, func() (size int, value cache.Value) {
+ ch = t.cache.Get(0, uint64(f.fd.Num), func() (size int, value cache.Value) {
var r storage.Reader
- r, err = f.file.Open()
+ r, err = t.s.stor.Open(f.fd)
if err != nil {
return 0, nil
}
var bcache *cache.CacheGetter
if t.bcache != nil {
- bcache = &cache.CacheGetter{Cache: t.bcache, NS: num}
+ bcache = &cache.CacheGetter{Cache: t.bcache, NS: uint64(f.fd.Num)}
}
var tr *table.Reader
- tr, err = table.NewReader(r, int64(f.size), storage.NewFileInfo(f.file), bcache, t.bpool, t.s.o.Options)
+ tr, err = table.NewReader(r, f.size, f.fd, bcache, t.bpool, t.s.o.Options)
if err != nil {
r.Close()
return 0, nil
@@ -413,15 +417,14 @@ func (t *tOps) newIterator(f *tFile, slice *util.Range, ro *opt.ReadOptions) ite
// Removes table from persistent storage. It waits until
// no one use the the table.
func (t *tOps) remove(f *tFile) {
- num := f.file.Num()
- t.cache.Delete(0, num, func() {
- if err := f.file.Remove(); err != nil {
- t.s.logf("table@remove removing @%d %q", num, err)
+ t.cache.Delete(0, uint64(f.fd.Num), func() {
+ if err := t.s.stor.Remove(f.fd); err != nil {
+ t.s.logf("table@remove removing @%d %q", f.fd.Num, err)
} else {
- t.s.logf("table@remove removed @%d", num)
+ t.s.logf("table@remove removed @%d", f.fd.Num)
}
if t.bcache != nil {
- t.bcache.EvictNS(num)
+ t.bcache.EvictNS(uint64(f.fd.Num))
}
})
}
@@ -441,22 +444,27 @@ func newTableOps(s *session) *tOps {
var (
cacher cache.Cacher
bcache *cache.Cache
+ bpool *util.BufferPool
)
if s.o.GetOpenFilesCacheCapacity() > 0 {
cacher = cache.NewLRU(s.o.GetOpenFilesCacheCapacity())
}
- if !s.o.DisableBlockCache {
+ if !s.o.GetDisableBlockCache() {
var bcacher cache.Cacher
if s.o.GetBlockCacheCapacity() > 0 {
bcacher = cache.NewLRU(s.o.GetBlockCacheCapacity())
}
bcache = cache.NewCache(bcacher)
}
+ if !s.o.GetDisableBufferPool() {
+ bpool = util.NewBufferPool(s.o.GetBlockSize() + 5)
+ }
return &tOps{
s: s,
+ noSync: s.o.GetNoSync(),
cache: cache.NewCache(cacher),
bcache: bcache,
- bpool: util.NewBufferPool(s.o.GetBlockSize() + 5),
+ bpool: bpool,
}
}
@@ -465,9 +473,9 @@ func newTableOps(s *session) *tOps {
type tWriter struct {
t *tOps
- file storage.File
- w storage.Writer
- tw *table.Writer
+ fd storage.FileDesc
+ w storage.Writer
+ tw *table.Writer
first, last []byte
}
@@ -501,20 +509,21 @@ func (w *tWriter) finish() (f *tFile, err error) {
if err != nil {
return
}
- err = w.w.Sync()
- if err != nil {
- return
+ if !w.t.noSync {
+ err = w.w.Sync()
+ if err != nil {
+ return
+ }
}
- f = newTableFile(w.file, uint64(w.tw.BytesLen()), iKey(w.first), iKey(w.last))
+ f = newTableFile(w.fd, int64(w.tw.BytesLen()), iKey(w.first), iKey(w.last))
return
}
// Drops the table.
func (w *tWriter) drop() {
w.close()
- w.file.Remove()
- w.t.s.reuseFileNum(w.file.Num())
- w.file = nil
+ w.t.s.stor.Remove(w.fd)
+ w.t.s.reuseFileNum(w.fd.Num)
w.tw = nil
w.first = nil
w.last = nil
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go
deleted file mode 100644
index 00e6f9eea..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/block_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package table
-
-import (
- "encoding/binary"
- "fmt"
-
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
-
- "github.com/syndtr/goleveldb/leveldb/comparer"
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/testutil"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-type blockTesting struct {
- tr *Reader
- b *block
-}
-
-func (t *blockTesting) TestNewIterator(slice *util.Range) iterator.Iterator {
- return t.tr.newBlockIter(t.b, nil, slice, false)
-}
-
-var _ = testutil.Defer(func() {
- Describe("Block", func() {
- Build := func(kv *testutil.KeyValue, restartInterval int) *blockTesting {
- // Building the block.
- bw := &blockWriter{
- restartInterval: restartInterval,
- scratch: make([]byte, 30),
- }
- kv.Iterate(func(i int, key, value []byte) {
- bw.append(key, value)
- })
- bw.finish()
-
- // Opening the block.
- data := bw.buf.Bytes()
- restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:]))
- return &blockTesting{
- tr: &Reader{cmp: comparer.DefaultComparer},
- b: &block{
- data: data,
- restartsLen: restartsLen,
- restartsOffset: len(data) - (restartsLen+1)*4,
- },
- }
- }
-
- Describe("read test", func() {
- for restartInterval := 1; restartInterval <= 5; restartInterval++ {
- Describe(fmt.Sprintf("with restart interval of %d", restartInterval), func() {
- kv := &testutil.KeyValue{}
- Text := func() string {
- return fmt.Sprintf("and %d keys", kv.Len())
- }
-
- Test := func() {
- // Make block.
- br := Build(kv, restartInterval)
- // Do testing.
- testutil.KeyValueTesting(nil, kv.Clone(), br, nil, nil)
- }
-
- Describe(Text(), Test)
-
- kv.PutString("", "empty")
- Describe(Text(), Test)
-
- kv.PutString("a1", "foo")
- Describe(Text(), Test)
-
- kv.PutString("a2", "v")
- Describe(Text(), Test)
-
- kv.PutString("a3qqwrkks", "hello")
- Describe(Text(), Test)
-
- kv.PutString("a4", "bar")
- Describe(Text(), Test)
-
- kv.PutString("a5111111", "v5")
- kv.PutString("a6", "")
- kv.PutString("a7", "v7")
- kv.PutString("a8", "vvvvvvvvvvvvvvvvvvvvvv8")
- kv.PutString("b", "v9")
- kv.PutString("c9", "v9")
- kv.PutString("c91", "v9")
- kv.PutString("d0", "v9")
- Describe(Text(), Test)
- })
- }
- })
-
- Describe("out-of-bound slice test", func() {
- kv := &testutil.KeyValue{}
- kv.PutString("k1", "v1")
- kv.PutString("k2", "v2")
- kv.PutString("k3abcdefgg", "v3")
- kv.PutString("k4", "v4")
- kv.PutString("k5", "v5")
- for restartInterval := 1; restartInterval <= 5; restartInterval++ {
- Describe(fmt.Sprintf("with restart interval of %d", restartInterval), func() {
- // Make block.
- bt := Build(kv, restartInterval)
-
- Test := func(r *util.Range) func(done Done) {
- return func(done Done) {
- iter := bt.TestNewIterator(r)
- Expect(iter.Error()).ShouldNot(HaveOccurred())
-
- t := testutil.IteratorTesting{
- KeyValue: kv.Clone(),
- Iter: iter,
- }
-
- testutil.DoIteratorTesting(&t)
- iter.Release()
- done <- true
- }
- }
-
- It("Should do iterations and seeks correctly #0",
- Test(&util.Range{Start: []byte("k0"), Limit: []byte("k6")}), 2.0)
-
- It("Should do iterations and seeks correctly #1",
- Test(&util.Range{Start: []byte(""), Limit: []byte("zzzzzzz")}), 2.0)
- })
- }
- })
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/reader.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/reader.go
index 6f38e84b3..caeac96b5 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/reader.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/reader.go
@@ -14,7 +14,7 @@ import (
"strings"
"sync"
- "github.com/syndtr/gosnappy/snappy"
+ "github.com/golang/snappy"
"github.com/syndtr/goleveldb/leveldb/cache"
"github.com/syndtr/goleveldb/leveldb/comparer"
@@ -507,7 +507,7 @@ func (i *indexIter) Get() iterator.Iterator {
// Reader is a table reader.
type Reader struct {
mu sync.RWMutex
- fi *storage.FileInfo
+ fd storage.FileDesc
reader io.ReaderAt
cache *cache.CacheGetter
err error
@@ -539,7 +539,7 @@ func (r *Reader) blockKind(bh blockHandle) string {
}
func (r *Reader) newErrCorrupted(pos, size int64, kind, reason string) error {
- return &errors.ErrCorrupted{File: r.fi, Err: &ErrCorrupted{Pos: pos, Size: size, Kind: kind, Reason: reason}}
+ return &errors.ErrCorrupted{Fd: r.fd, Err: &ErrCorrupted{Pos: pos, Size: size, Kind: kind, Reason: reason}}
}
func (r *Reader) newErrCorruptedBH(bh blockHandle, reason string) error {
@@ -551,7 +551,7 @@ func (r *Reader) fixErrCorruptedBH(bh blockHandle, err error) error {
cerr.Pos = int64(bh.offset)
cerr.Size = int64(bh.length)
cerr.Kind = r.blockKind(bh)
- return &errors.ErrCorrupted{File: r.fi, Err: cerr}
+ return &errors.ErrCorrupted{Fd: r.fd, Err: cerr}
}
return err
}
@@ -988,13 +988,13 @@ func (r *Reader) Release() {
// The fi, cache and bpool is optional and can be nil.
//
// The returned table reader instance is goroutine-safe.
-func NewReader(f io.ReaderAt, size int64, fi *storage.FileInfo, cache *cache.CacheGetter, bpool *util.BufferPool, o *opt.Options) (*Reader, error) {
+func NewReader(f io.ReaderAt, size int64, fd storage.FileDesc, cache *cache.CacheGetter, bpool *util.BufferPool, o *opt.Options) (*Reader, error) {
if f == nil {
return nil, errors.New("leveldb/table: nil file")
}
r := &Reader{
- fi: fi,
+ fd: fd,
reader: f,
cache: cache,
bpool: bpool,
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go
deleted file mode 100644
index 6465da6e3..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_suite_test.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package table
-
-import (
- "testing"
-
- "github.com/syndtr/goleveldb/leveldb/testutil"
-)
-
-func TestTable(t *testing.T) {
- testutil.RunSuite(t, "Table Suite")
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go
deleted file mode 100644
index 4b59b31f5..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/table_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package table
-
-import (
- "bytes"
-
- . "github.com/onsi/ginkgo"
- . "github.com/onsi/gomega"
-
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/testutil"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-type tableWrapper struct {
- *Reader
-}
-
-func (t tableWrapper) TestFind(key []byte) (rkey, rvalue []byte, err error) {
- return t.Reader.Find(key, false, nil)
-}
-
-func (t tableWrapper) TestGet(key []byte) (value []byte, err error) {
- return t.Reader.Get(key, nil)
-}
-
-func (t tableWrapper) TestNewIterator(slice *util.Range) iterator.Iterator {
- return t.Reader.NewIterator(slice, nil)
-}
-
-var _ = testutil.Defer(func() {
- Describe("Table", func() {
- Describe("approximate offset test", func() {
- var (
- buf = &bytes.Buffer{}
- o = &opt.Options{
- BlockSize: 1024,
- Compression: opt.NoCompression,
- }
- )
-
- // Building the table.
- tw := NewWriter(buf, o)
- tw.Append([]byte("k01"), []byte("hello"))
- tw.Append([]byte("k02"), []byte("hello2"))
- tw.Append([]byte("k03"), bytes.Repeat([]byte{'x'}, 10000))
- tw.Append([]byte("k04"), bytes.Repeat([]byte{'x'}, 200000))
- tw.Append([]byte("k05"), bytes.Repeat([]byte{'x'}, 300000))
- tw.Append([]byte("k06"), []byte("hello3"))
- tw.Append([]byte("k07"), bytes.Repeat([]byte{'x'}, 100000))
- err := tw.Close()
-
- It("Should be able to approximate offset of a key correctly", func() {
- Expect(err).ShouldNot(HaveOccurred())
-
- tr, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, nil, o)
- Expect(err).ShouldNot(HaveOccurred())
- CheckOffset := func(key string, expect, threshold int) {
- offset, err := tr.OffsetOf([]byte(key))
- Expect(err).ShouldNot(HaveOccurred())
- Expect(offset).Should(BeNumerically("~", expect, threshold), "Offset of key %q", key)
- }
-
- CheckOffset("k0", 0, 0)
- CheckOffset("k01a", 0, 0)
- CheckOffset("k02", 0, 0)
- CheckOffset("k03", 0, 0)
- CheckOffset("k04", 10000, 1000)
- CheckOffset("k04a", 210000, 1000)
- CheckOffset("k05", 210000, 1000)
- CheckOffset("k06", 510000, 1000)
- CheckOffset("k07", 510000, 1000)
- CheckOffset("xyz", 610000, 2000)
- })
- })
-
- Describe("read test", func() {
- Build := func(kv testutil.KeyValue) testutil.DB {
- o := &opt.Options{
- BlockSize: 512,
- BlockRestartInterval: 3,
- }
- buf := &bytes.Buffer{}
-
- // Building the table.
- tw := NewWriter(buf, o)
- kv.Iterate(func(i int, key, value []byte) {
- tw.Append(key, value)
- })
- tw.Close()
-
- // Opening the table.
- tr, _ := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()), nil, nil, nil, o)
- return tableWrapper{tr}
- }
- Test := func(kv *testutil.KeyValue, body func(r *Reader)) func() {
- return func() {
- db := Build(*kv)
- if body != nil {
- body(db.(tableWrapper).Reader)
- }
- testutil.KeyValueTesting(nil, *kv, db, nil, nil)
- }
- }
-
- testutil.AllKeyValueTesting(nil, Build, nil, nil)
- Describe("with one key per block", Test(testutil.KeyValue_Generate(nil, 9, 1, 10, 512, 512), func(r *Reader) {
- It("should have correct blocks number", func() {
- indexBlock, err := r.readBlock(r.indexBH, true)
- Expect(err).To(BeNil())
- Expect(indexBlock.restartsLen).Should(Equal(9))
- })
- }))
- })
- })
-})
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/writer.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/writer.go
index 274c95fad..274dee6da 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/writer.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/table/writer.go
@@ -12,7 +12,7 @@ import (
"fmt"
"io"
- "github.com/syndtr/gosnappy/snappy"
+ "github.com/golang/snappy"
"github.com/syndtr/goleveldb/leveldb/comparer"
"github.com/syndtr/goleveldb/leveldb/filter"
@@ -167,11 +167,7 @@ func (w *Writer) writeBlock(buf *util.Buffer, compression opt.Compression) (bh b
if n := snappy.MaxEncodedLen(buf.Len()) + blockTrailerLen; len(w.compressionScratch) < n {
w.compressionScratch = make([]byte, n)
}
- var compressed []byte
- compressed, err = snappy.Encode(w.compressionScratch, buf.Bytes())
- if err != nil {
- return
- }
+ compressed := snappy.Encode(w.compressionScratch, buf.Bytes())
n := len(compressed)
b = compressed[:n+blockTrailerLen]
b[n] = blockTypeSnappyCompression
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go
index a0b58f0e7..19d29dfc4 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/kvtest.go
@@ -17,6 +17,84 @@ import (
"github.com/syndtr/goleveldb/leveldb/util"
)
+func TestFind(db Find, kv KeyValue) {
+ ShuffledIndex(nil, kv.Len(), 1, func(i int) {
+ key_, key, value := kv.IndexInexact(i)
+
+ // Using exact key.
+ rkey, rvalue, err := db.TestFind(key)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
+ Expect(rkey).Should(Equal(key), "Key")
+ Expect(rvalue).Should(Equal(value), "Value for key %q", key)
+
+ // Using inexact key.
+ rkey, rvalue, err = db.TestFind(key_)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q (%q)", key_, key)
+ Expect(rkey).Should(Equal(key))
+ Expect(rvalue).Should(Equal(value), "Value for key %q (%q)", key_, key)
+ })
+}
+
+func TestFindAfterLast(db Find, kv KeyValue) {
+ var key []byte
+ if kv.Len() > 0 {
+ key_, _ := kv.Index(kv.Len() - 1)
+ key = BytesAfter(key_)
+ }
+ rkey, _, err := db.TestFind(key)
+ Expect(err).Should(HaveOccurred(), "Find for key %q yield key %q", key, rkey)
+ Expect(err).Should(Equal(errors.ErrNotFound))
+}
+
+func TestGet(db Get, kv KeyValue) {
+ ShuffledIndex(nil, kv.Len(), 1, func(i int) {
+ key_, key, value := kv.IndexInexact(i)
+
+ // Using exact key.
+ rvalue, err := db.TestGet(key)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
+ Expect(rvalue).Should(Equal(value), "Value for key %q", key)
+
+ // Using inexact key.
+ if len(key_) > 0 {
+ _, err = db.TestGet(key_)
+ Expect(err).Should(HaveOccurred(), "Error for key %q", key_)
+ Expect(err).Should(Equal(errors.ErrNotFound))
+ }
+ })
+}
+
+func TestHas(db Has, kv KeyValue) {
+ ShuffledIndex(nil, kv.Len(), 1, func(i int) {
+ key_, key, _ := kv.IndexInexact(i)
+
+ // Using exact key.
+ ret, err := db.TestHas(key)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
+ Expect(ret).Should(BeTrue(), "False for key %q", key)
+
+ // Using inexact key.
+ if len(key_) > 0 {
+ ret, err = db.TestHas(key_)
+ Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key_)
+ Expect(ret).ShouldNot(BeTrue(), "True for key %q", key)
+ }
+ })
+}
+
+func TestIter(db NewIterator, r *util.Range, kv KeyValue) {
+ iter := db.TestNewIterator(r)
+ Expect(iter.Error()).ShouldNot(HaveOccurred())
+
+ t := IteratorTesting{
+ KeyValue: kv,
+ Iter: iter,
+ }
+
+ DoIteratorTesting(&t)
+ iter.Release()
+}
+
func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB, teardown func(DB)) {
if rnd == nil {
rnd = NewRand()
@@ -35,122 +113,68 @@ func KeyValueTesting(rnd *rand.Rand, kv KeyValue, p DB, setup func(KeyValue) DB,
It("Should find all keys with Find", func() {
if db, ok := p.(Find); ok {
- ShuffledIndex(nil, kv.Len(), 1, func(i int) {
- key_, key, value := kv.IndexInexact(i)
-
- // Using exact key.
- rkey, rvalue, err := db.TestFind(key)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
- Expect(rkey).Should(Equal(key), "Key")
- Expect(rvalue).Should(Equal(value), "Value for key %q", key)
-
- // Using inexact key.
- rkey, rvalue, err = db.TestFind(key_)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q (%q)", key_, key)
- Expect(rkey).Should(Equal(key))
- Expect(rvalue).Should(Equal(value), "Value for key %q (%q)", key_, key)
- })
+ TestFind(db, kv)
}
})
- It("Should return error if the key is not present", func() {
+ It("Should return error if Find on key after the last", func() {
if db, ok := p.(Find); ok {
- var key []byte
- if kv.Len() > 0 {
- key_, _ := kv.Index(kv.Len() - 1)
- key = BytesAfter(key_)
- }
- rkey, _, err := db.TestFind(key)
- Expect(err).Should(HaveOccurred(), "Find for key %q yield key %q", key, rkey)
- Expect(err).Should(Equal(errors.ErrNotFound))
+ TestFindAfterLast(db, kv)
}
})
It("Should only find exact key with Get", func() {
if db, ok := p.(Get); ok {
- ShuffledIndex(nil, kv.Len(), 1, func(i int) {
- key_, key, value := kv.IndexInexact(i)
-
- // Using exact key.
- rvalue, err := db.TestGet(key)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
- Expect(rvalue).Should(Equal(value), "Value for key %q", key)
-
- // Using inexact key.
- if len(key_) > 0 {
- _, err = db.TestGet(key_)
- Expect(err).Should(HaveOccurred(), "Error for key %q", key_)
- Expect(err).Should(Equal(errors.ErrNotFound))
- }
- })
+ TestGet(db, kv)
}
})
It("Should only find present key with Has", func() {
if db, ok := p.(Has); ok {
- ShuffledIndex(nil, kv.Len(), 1, func(i int) {
- key_, key, _ := kv.IndexInexact(i)
-
- // Using exact key.
- ret, err := db.TestHas(key)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key)
- Expect(ret).Should(BeTrue(), "False for key %q", key)
-
- // Using inexact key.
- if len(key_) > 0 {
- ret, err = db.TestHas(key_)
- Expect(err).ShouldNot(HaveOccurred(), "Error for key %q", key_)
- Expect(ret).ShouldNot(BeTrue(), "True for key %q", key)
- }
- })
+ TestHas(db, kv)
}
})
- TestIter := func(r *util.Range, _kv KeyValue) {
+ It("Should iterates and seeks correctly", func(done Done) {
if db, ok := p.(NewIterator); ok {
- iter := db.TestNewIterator(r)
- Expect(iter.Error()).ShouldNot(HaveOccurred())
-
- t := IteratorTesting{
- KeyValue: _kv,
- Iter: iter,
- }
-
- DoIteratorTesting(&t)
- iter.Release()
+ TestIter(db, nil, kv.Clone())
}
- }
-
- It("Should iterates and seeks correctly", func(done Done) {
- TestIter(nil, kv.Clone())
done <- true
}, 3.0)
- RandomIndex(rnd, kv.Len(), Min(kv.Len(), 50), func(i int) {
- type slice struct {
- r *util.Range
- start, limit int
- }
+ It("Should iterates and seeks slice correctly", func(done Done) {
+ if db, ok := p.(NewIterator); ok {
+ RandomIndex(rnd, kv.Len(), Min(kv.Len(), 50), func(i int) {
+ type slice struct {
+ r *util.Range
+ start, limit int
+ }
- key_, _, _ := kv.IndexInexact(i)
- for _, x := range []slice{
- {&util.Range{Start: key_, Limit: nil}, i, kv.Len()},
- {&util.Range{Start: nil, Limit: key_}, 0, i},
- } {
- It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", x.start, x.limit), func(done Done) {
- TestIter(x.r, kv.Slice(x.start, x.limit))
- done <- true
- }, 3.0)
+ key_, _, _ := kv.IndexInexact(i)
+ for _, x := range []slice{
+ {&util.Range{Start: key_, Limit: nil}, i, kv.Len()},
+ {&util.Range{Start: nil, Limit: key_}, 0, i},
+ } {
+ By(fmt.Sprintf("Random index of %d .. %d", x.start, x.limit), func() {
+ TestIter(db, x.r, kv.Slice(x.start, x.limit))
+ })
+ }
+ })
}
- })
+ done <- true
+ }, 50.0)
- RandomRange(rnd, kv.Len(), Min(kv.Len(), 50), func(start, limit int) {
- It(fmt.Sprintf("Should iterates and seeks correctly of a slice %d .. %d", start, limit), func(done Done) {
- r := kv.Range(start, limit)
- TestIter(&r, kv.Slice(start, limit))
- done <- true
- }, 3.0)
- })
+ It("Should iterates and seeks slice correctly", func(done Done) {
+ if db, ok := p.(NewIterator); ok {
+ RandomRange(rnd, kv.Len(), Min(kv.Len(), 50), func(start, limit int) {
+ By(fmt.Sprintf("Random range of %d .. %d", start, limit), func() {
+ r := kv.Range(start, limit)
+ TestIter(db, &r, kv.Slice(start, limit))
+ })
+ })
+ }
+ done <- true
+ }, 50.0)
}
func AllKeyValueTesting(rnd *rand.Rand, body, setup func(KeyValue) DB, teardown func(DB)) {
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go
index 59c496d54..1d9163ea4 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil/storage.go
@@ -10,6 +10,7 @@ import (
"bytes"
"fmt"
"io"
+ "math/rand"
"os"
"path/filepath"
"runtime"
@@ -24,8 +25,8 @@ import (
var (
storageMu sync.Mutex
- storageUseFS bool = true
- storageKeepFS bool = false
+ storageUseFS = true
+ storageKeepFS = false
storageNum int
)
@@ -35,6 +36,7 @@ const (
ModeOpen StorageMode = 1 << iota
ModeCreate
ModeRemove
+ ModeRename
ModeRead
ModeWrite
ModeSync
@@ -45,6 +47,7 @@ const (
modeOpen = iota
modeCreate
modeRemove
+ modeRename
modeRead
modeWrite
modeSync
@@ -73,6 +76,8 @@ func flattenType(m StorageMode, t storage.FileType) int {
x = modeCreate
case ModeRemove:
x = modeRemove
+ case ModeRename:
+ x = modeRename
case ModeRead:
x = modeRead
case ModeWrite:
@@ -121,6 +126,8 @@ func listFlattenType(m StorageMode, t storage.FileType) []int {
add(modeCreate)
case m&ModeRemove != 0:
add(modeRemove)
+ case m&ModeRename != 0:
+ add(modeRename)
case m&ModeRead != 0:
add(modeRead)
case m&ModeWrite != 0:
@@ -133,15 +140,15 @@ func listFlattenType(m StorageMode, t storage.FileType) []int {
return ret
}
-func packFile(num uint64, t storage.FileType) uint64 {
- if num>>(64-typeCount) != 0 {
+func packFile(fd storage.FileDesc) uint64 {
+ if fd.Num>>(63-typeCount) != 0 {
panic("overflow")
}
- return num<<typeCount | uint64(t)
+ return uint64(fd.Num<<typeCount) | uint64(fd.Type)
}
-func unpackFile(x uint64) (uint64, storage.FileType) {
- return x >> typeCount, storage.FileType(x) & storage.TypeAll
+func unpackFile(x uint64) storage.FileDesc {
+ return storage.FileDesc{storage.FileType(x) & storage.TypeAll, int64(x >> typeCount)}
}
type emulatedError struct {
@@ -163,189 +170,98 @@ func (l storageLock) Release() {
}
type reader struct {
- f *file
+ s *Storage
+ fd storage.FileDesc
storage.Reader
}
func (r *reader) Read(p []byte) (n int, err error) {
- err = r.f.s.emulateError(ModeRead, r.f.Type())
+ err = r.s.emulateError(ModeRead, r.fd.Type)
if err == nil {
- r.f.s.stall(ModeRead, r.f.Type())
+ r.s.stall(ModeRead, r.fd.Type)
n, err = r.Reader.Read(p)
}
- r.f.s.count(ModeRead, r.f.Type(), n)
+ r.s.count(ModeRead, r.fd.Type, n)
if err != nil && err != io.EOF {
- r.f.s.logI("read error, num=%d type=%v n=%d err=%v", r.f.Num(), r.f.Type(), n, err)
+ r.s.logI("read error, fd=%s n=%d err=%v", r.fd, n, err)
}
return
}
func (r *reader) ReadAt(p []byte, off int64) (n int, err error) {
- err = r.f.s.emulateError(ModeRead, r.f.Type())
+ err = r.s.emulateError(ModeRead, r.fd.Type)
if err == nil {
- r.f.s.stall(ModeRead, r.f.Type())
+ r.s.stall(ModeRead, r.fd.Type)
n, err = r.Reader.ReadAt(p, off)
}
- r.f.s.count(ModeRead, r.f.Type(), n)
+ r.s.count(ModeRead, r.fd.Type, n)
if err != nil && err != io.EOF {
- r.f.s.logI("readAt error, num=%d type=%v offset=%d n=%d err=%v", r.f.Num(), r.f.Type(), off, n, err)
+ r.s.logI("readAt error, fd=%s offset=%d n=%d err=%v", r.fd, off, n, err)
}
return
}
func (r *reader) Close() (err error) {
- return r.f.doClose(r.Reader)
+ return r.s.fileClose(r.fd, r.Reader)
}
type writer struct {
- f *file
+ s *Storage
+ fd storage.FileDesc
storage.Writer
}
func (w *writer) Write(p []byte) (n int, err error) {
- err = w.f.s.emulateError(ModeWrite, w.f.Type())
+ err = w.s.emulateError(ModeWrite, w.fd.Type)
if err == nil {
- w.f.s.stall(ModeWrite, w.f.Type())
+ w.s.stall(ModeWrite, w.fd.Type)
n, err = w.Writer.Write(p)
}
- w.f.s.count(ModeWrite, w.f.Type(), n)
+ w.s.count(ModeWrite, w.fd.Type, n)
if err != nil && err != io.EOF {
- w.f.s.logI("write error, num=%d type=%v n=%d err=%v", w.f.Num(), w.f.Type(), n, err)
+ w.s.logI("write error, fd=%s n=%d err=%v", w.fd, n, err)
}
return
}
func (w *writer) Sync() (err error) {
- err = w.f.s.emulateError(ModeSync, w.f.Type())
+ err = w.s.emulateError(ModeSync, w.fd.Type)
if err == nil {
- w.f.s.stall(ModeSync, w.f.Type())
+ w.s.stall(ModeSync, w.fd.Type)
err = w.Writer.Sync()
}
- w.f.s.count(ModeSync, w.f.Type(), 0)
+ w.s.count(ModeSync, w.fd.Type, 0)
if err != nil {
- w.f.s.logI("sync error, num=%d type=%v err=%v", w.f.Num(), w.f.Type(), err)
+ w.s.logI("sync error, fd=%s err=%v", w.fd, err)
}
return
}
func (w *writer) Close() (err error) {
- return w.f.doClose(w.Writer)
-}
-
-type file struct {
- s *Storage
- storage.File
-}
-
-func (f *file) pack() uint64 {
- return packFile(f.Num(), f.Type())
-}
-
-func (f *file) assertOpen() {
- ExpectWithOffset(2, f.s.opens).NotTo(HaveKey(f.pack()), "File open, num=%d type=%v writer=%v", f.Num(), f.Type(), f.s.opens[f.pack()])
-}
-
-func (f *file) doClose(closer io.Closer) (err error) {
- err = f.s.emulateError(ModeClose, f.Type())
- if err == nil {
- f.s.stall(ModeClose, f.Type())
- }
- f.s.mu.Lock()
- defer f.s.mu.Unlock()
- if err == nil {
- ExpectWithOffset(2, f.s.opens).To(HaveKey(f.pack()), "File closed, num=%d type=%v", f.Num(), f.Type())
- err = closer.Close()
- }
- f.s.countNB(ModeClose, f.Type(), 0)
- writer := f.s.opens[f.pack()]
- if err != nil {
- f.s.logISkip(1, "file close failed, num=%d type=%v writer=%v err=%v", f.Num(), f.Type(), writer, err)
- } else {
- f.s.logISkip(1, "file closed, num=%d type=%v writer=%v", f.Num(), f.Type(), writer)
- delete(f.s.opens, f.pack())
- }
- return
-}
-
-func (f *file) Open() (r storage.Reader, err error) {
- err = f.s.emulateError(ModeOpen, f.Type())
- if err == nil {
- f.s.stall(ModeOpen, f.Type())
- }
- f.s.mu.Lock()
- defer f.s.mu.Unlock()
- if err == nil {
- f.assertOpen()
- f.s.countNB(ModeOpen, f.Type(), 0)
- r, err = f.File.Open()
- }
- if err != nil {
- f.s.logI("file open failed, num=%d type=%v err=%v", f.Num(), f.Type(), err)
- } else {
- f.s.logI("file opened, num=%d type=%v", f.Num(), f.Type())
- f.s.opens[f.pack()] = false
- r = &reader{f, r}
- }
- return
-}
-
-func (f *file) Create() (w storage.Writer, err error) {
- err = f.s.emulateError(ModeCreate, f.Type())
- if err == nil {
- f.s.stall(ModeCreate, f.Type())
- }
- f.s.mu.Lock()
- defer f.s.mu.Unlock()
- if err == nil {
- f.assertOpen()
- f.s.countNB(ModeCreate, f.Type(), 0)
- w, err = f.File.Create()
- }
- if err != nil {
- f.s.logI("file create failed, num=%d type=%v err=%v", f.Num(), f.Type(), err)
- } else {
- f.s.logI("file created, num=%d type=%v", f.Num(), f.Type())
- f.s.opens[f.pack()] = true
- w = &writer{f, w}
- }
- return
-}
-
-func (f *file) Remove() (err error) {
- err = f.s.emulateError(ModeRemove, f.Type())
- if err == nil {
- f.s.stall(ModeRemove, f.Type())
- }
- f.s.mu.Lock()
- defer f.s.mu.Unlock()
- if err == nil {
- f.assertOpen()
- f.s.countNB(ModeRemove, f.Type(), 0)
- err = f.File.Remove()
- }
- if err != nil {
- f.s.logI("file remove failed, num=%d type=%v err=%v", f.Num(), f.Type(), err)
- } else {
- f.s.logI("file removed, num=%d type=%v", f.Num(), f.Type())
- }
- return
+ return w.s.fileClose(w.fd, w.Writer)
}
type Storage struct {
storage.Storage
- closeFn func() error
+ path string
+ onClose func() (preserve bool, err error)
+ onLog func(str string)
lmu sync.Mutex
lb bytes.Buffer
- mu sync.Mutex
+ mu sync.Mutex
+ rand *rand.Rand
// Open files, true=writer, false=reader
- opens map[uint64]bool
- counters [flattenCount]int
- bytesCounter [flattenCount]int64
- emulatedError [flattenCount]error
- stallCond sync.Cond
- stalled [flattenCount]bool
+ opens map[uint64]bool
+ counters [flattenCount]int
+ bytesCounter [flattenCount]int64
+ emulatedError [flattenCount]error
+ emulatedErrorOnce [flattenCount]bool
+ emulatedRandomError [flattenCount]error
+ emulatedRandomErrorProb [flattenCount]float64
+ stallCond sync.Cond
+ stalled [flattenCount]bool
}
func (s *Storage) log(skip int, str string) {
@@ -374,7 +290,12 @@ func (s *Storage) log(skip int, str string) {
}
s.lb.WriteString(line)
}
- s.lb.WriteByte('\n')
+ if s.onLog != nil {
+ s.onLog(s.lb.String())
+ s.lb.Reset()
+ } else {
+ s.lb.WriteByte('\n')
+ }
}
func (s *Storage) logISkip(skip int, format string, args ...interface{}) {
@@ -395,74 +316,220 @@ func (s *Storage) logI(format string, args ...interface{}) {
s.logISkip(1, format, args...)
}
+func (s *Storage) OnLog(onLog func(log string)) {
+ s.lmu.Lock()
+ s.onLog = onLog
+ if s.lb.Len() != 0 {
+ log := s.lb.String()
+ s.onLog(log[:len(log)-1])
+ s.lb.Reset()
+ }
+ s.lmu.Unlock()
+}
+
func (s *Storage) Log(str string) {
s.log(1, "Log: "+str)
s.Storage.Log(str)
}
-func (s *Storage) Lock() (r util.Releaser, err error) {
- r, err = s.Storage.Lock()
+func (s *Storage) Lock() (l storage.Lock, err error) {
+ l, err = s.Storage.Lock()
if err != nil {
s.logI("storage locking failed, err=%v", err)
} else {
s.logI("storage locked")
- r = storageLock{s, r}
+ l = storageLock{s, l}
}
return
}
-func (s *Storage) GetFile(num uint64, t storage.FileType) storage.File {
- return &file{s, s.Storage.GetFile(num, t)}
-}
-
-func (s *Storage) GetFiles(t storage.FileType) (files []storage.File, err error) {
- rfiles, err := s.Storage.GetFiles(t)
+func (s *Storage) List(t storage.FileType) (fds []storage.FileDesc, err error) {
+ fds, err = s.Storage.List(t)
if err != nil {
- s.logI("get files failed, err=%v", err)
+ s.logI("list failed, err=%v", err)
return
}
- files = make([]storage.File, len(rfiles))
- for i, f := range rfiles {
- files[i] = &file{s, f}
- }
- s.logI("get files, type=0x%x count=%d", int(t), len(files))
+ s.logI("list, type=0x%x count=%d", int(t), len(fds))
return
}
-func (s *Storage) GetManifest() (f storage.File, err error) {
- manifest, err := s.Storage.GetManifest()
+func (s *Storage) GetMeta() (fd storage.FileDesc, err error) {
+ fd, err = s.Storage.GetMeta()
if err != nil {
if !os.IsNotExist(err) {
- s.logI("get manifest failed, err=%v", err)
+ s.logI("get meta failed, err=%v", err)
}
return
}
- s.logI("get manifest, num=%d", manifest.Num())
- return &file{s, manifest}, nil
+ s.logI("get meta, fd=%s", fd)
+ return
}
-func (s *Storage) SetManifest(f storage.File) error {
- f_, ok := f.(*file)
- ExpectWithOffset(1, ok).To(BeTrue())
- ExpectWithOffset(1, f_.Type()).To(Equal(storage.TypeManifest))
- err := s.Storage.SetManifest(f_.File)
+func (s *Storage) SetMeta(fd storage.FileDesc) error {
+ ExpectWithOffset(1, fd.Type).To(Equal(storage.TypeManifest))
+ err := s.Storage.SetMeta(fd)
if err != nil {
- s.logI("set manifest failed, err=%v", err)
+ s.logI("set meta failed, fd=%s err=%v", fd, err)
} else {
- s.logI("set manifest, num=%d", f_.Num())
+ s.logI("set meta, fd=%s", fd)
}
return err
}
+func (s *Storage) fileClose(fd storage.FileDesc, closer io.Closer) (err error) {
+ err = s.emulateError(ModeClose, fd.Type)
+ if err == nil {
+ s.stall(ModeClose, fd.Type)
+ }
+ x := packFile(fd)
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if err == nil {
+ ExpectWithOffset(2, s.opens).To(HaveKey(x), "File closed, fd=%s", fd)
+ err = closer.Close()
+ }
+ s.countNB(ModeClose, fd.Type, 0)
+ writer := s.opens[x]
+ if err != nil {
+ s.logISkip(1, "file close failed, fd=%s writer=%v err=%v", fd, writer, err)
+ } else {
+ s.logISkip(1, "file closed, fd=%s writer=%v", fd, writer)
+ delete(s.opens, x)
+ }
+ return
+}
+
+func (s *Storage) assertOpen(fd storage.FileDesc) {
+ x := packFile(fd)
+ ExpectWithOffset(2, s.opens).NotTo(HaveKey(x), "File open, fd=%s writer=%v", fd, s.opens[x])
+}
+
+func (s *Storage) Open(fd storage.FileDesc) (r storage.Reader, err error) {
+ err = s.emulateError(ModeOpen, fd.Type)
+ if err == nil {
+ s.stall(ModeOpen, fd.Type)
+ }
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if err == nil {
+ s.assertOpen(fd)
+ s.countNB(ModeOpen, fd.Type, 0)
+ r, err = s.Storage.Open(fd)
+ }
+ if err != nil {
+ s.logI("file open failed, fd=%s err=%v", fd, err)
+ } else {
+ s.logI("file opened, fd=%s", fd)
+ s.opens[packFile(fd)] = false
+ r = &reader{s, fd, r}
+ }
+ return
+}
+
+func (s *Storage) Create(fd storage.FileDesc) (w storage.Writer, err error) {
+ err = s.emulateError(ModeCreate, fd.Type)
+ if err == nil {
+ s.stall(ModeCreate, fd.Type)
+ }
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if err == nil {
+ s.assertOpen(fd)
+ s.countNB(ModeCreate, fd.Type, 0)
+ w, err = s.Storage.Create(fd)
+ }
+ if err != nil {
+ s.logI("file create failed, fd=%s err=%v", fd, err)
+ } else {
+ s.logI("file created, fd=%s", fd)
+ s.opens[packFile(fd)] = true
+ w = &writer{s, fd, w}
+ }
+ return
+}
+
+func (s *Storage) Remove(fd storage.FileDesc) (err error) {
+ err = s.emulateError(ModeRemove, fd.Type)
+ if err == nil {
+ s.stall(ModeRemove, fd.Type)
+ }
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if err == nil {
+ s.assertOpen(fd)
+ s.countNB(ModeRemove, fd.Type, 0)
+ err = s.Storage.Remove(fd)
+ }
+ if err != nil {
+ s.logI("file remove failed, fd=%s err=%v", fd, err)
+ } else {
+ s.logI("file removed, fd=%s", fd)
+ }
+ return
+}
+
+func (s *Storage) ForceRemove(fd storage.FileDesc) (err error) {
+ s.countNB(ModeRemove, fd.Type, 0)
+ if err = s.Storage.Remove(fd); err != nil {
+ s.logI("file remove failed (forced), fd=%s err=%v", fd, err)
+ } else {
+ s.logI("file removed (forced), fd=%s", fd)
+ }
+ return
+}
+
+func (s *Storage) Rename(oldfd, newfd storage.FileDesc) (err error) {
+ err = s.emulateError(ModeRename, oldfd.Type)
+ if err == nil {
+ s.stall(ModeRename, oldfd.Type)
+ }
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ if err == nil {
+ s.assertOpen(oldfd)
+ s.assertOpen(newfd)
+ s.countNB(ModeRename, oldfd.Type, 0)
+ err = s.Storage.Rename(oldfd, newfd)
+ }
+ if err != nil {
+ s.logI("file rename failed, oldfd=%s newfd=%s err=%v", oldfd, newfd, err)
+ } else {
+ s.logI("file renamed, oldfd=%s newfd=%s", oldfd, newfd)
+ }
+ return
+}
+
+func (s *Storage) ForceRename(oldfd, newfd storage.FileDesc) (err error) {
+ s.countNB(ModeRename, oldfd.Type, 0)
+ if err = s.Storage.Rename(oldfd, newfd); err != nil {
+ s.logI("file rename failed (forced), oldfd=%s newfd=%s err=%v", oldfd, newfd, err)
+ } else {
+ s.logI("file renamed (forced), oldfd=%s newfd=%s", oldfd, newfd)
+ }
+ return
+}
+
func (s *Storage) openFiles() string {
out := "Open files:"
for x, writer := range s.opens {
- num, t := unpackFile(x)
- out += fmt.Sprintf("\n · num=%d type=%v writer=%v", num, t, writer)
+ fd := unpackFile(x)
+ out += fmt.Sprintf("\n · fd=%s writer=%v", fd, writer)
}
return out
}
+func (s *Storage) CloseCheck() {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ ExpectWithOffset(1, s.opens).To(BeEmpty(), s.openFiles())
+}
+
+func (s *Storage) OnClose(onClose func() (preserve bool, err error)) {
+ s.mu.Lock()
+ s.onClose = onClose
+ s.mu.Unlock()
+}
+
func (s *Storage) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
@@ -473,9 +540,22 @@ func (s *Storage) Close() error {
} else {
s.logI("storage closed")
}
- if s.closeFn != nil {
- if err1 := s.closeFn(); err1 != nil {
- s.logI("close func error, err=%v", err1)
+ var preserve bool
+ if s.onClose != nil {
+ var err0 error
+ if preserve, err0 = s.onClose(); err0 != nil {
+ s.logI("onClose error, err=%v", err0)
+ }
+ }
+ if s.path != "" {
+ if storageKeepFS || preserve {
+ s.logI("storage is preserved, path=%v", s.path)
+ } else {
+ if err1 := os.RemoveAll(s.path); err1 != nil {
+ s.logI("cannot remove storage, err=%v", err1)
+ } else {
+ s.logI("storage has been removed")
+ }
}
}
return err
@@ -510,8 +590,14 @@ func (s *Storage) Counter(m StorageMode, t storage.FileType) (count int, bytes i
func (s *Storage) emulateError(m StorageMode, t storage.FileType) error {
s.mu.Lock()
defer s.mu.Unlock()
- err := s.emulatedError[flattenType(m, t)]
- if err != nil {
+ x := flattenType(m, t)
+ if err := s.emulatedError[x]; err != nil {
+ if s.emulatedErrorOnce[x] {
+ s.emulatedError[x] = nil
+ }
+ return emulatedError{err}
+ }
+ if err := s.emulatedRandomError[x]; err != nil && s.rand.Float64() < s.emulatedRandomErrorProb[x] {
return emulatedError{err}
}
return nil
@@ -522,6 +608,25 @@ func (s *Storage) EmulateError(m StorageMode, t storage.FileType, err error) {
defer s.mu.Unlock()
for _, x := range listFlattenType(m, t) {
s.emulatedError[x] = err
+ s.emulatedErrorOnce[x] = false
+ }
+}
+
+func (s *Storage) EmulateErrorOnce(m StorageMode, t storage.FileType, err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ for _, x := range listFlattenType(m, t) {
+ s.emulatedError[x] = err
+ s.emulatedErrorOnce[x] = true
+ }
+}
+
+func (s *Storage) EmulateRandomError(m StorageMode, t storage.FileType, prob float64, err error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ for _, x := range listFlattenType(m, t) {
+ s.emulatedRandomError[x] = err
+ s.emulatedRandomErrorProb[x] = prob
}
}
@@ -552,24 +657,20 @@ func (s *Storage) Release(m StorageMode, t storage.FileType) {
}
func NewStorage() *Storage {
- var stor storage.Storage
- var closeFn func() error
+ var (
+ stor storage.Storage
+ path string
+ )
if storageUseFS {
for {
storageMu.Lock()
num := storageNum
storageNum++
storageMu.Unlock()
- path := filepath.Join(os.TempDir(), fmt.Sprintf("goleveldb-test%d0%d0%d", os.Getuid(), os.Getpid(), num))
+ path = filepath.Join(os.TempDir(), fmt.Sprintf("goleveldb-test%d0%d0%d", os.Getuid(), os.Getpid(), num))
if _, err := os.Stat(path); os.IsNotExist(err) {
- stor, err = storage.OpenFile(path)
+ stor, err = storage.OpenFile(path, false)
ExpectWithOffset(1, err).NotTo(HaveOccurred(), "creating storage at %s", path)
- closeFn = func() error {
- if storageKeepFS {
- return nil
- }
- return os.RemoveAll(path)
- }
break
}
}
@@ -578,9 +679,16 @@ func NewStorage() *Storage {
}
s := &Storage{
Storage: stor,
- closeFn: closeFn,
+ path: path,
+ rand: NewRand(),
opens: make(map[uint64]bool),
}
s.stallCond.L = &s.mu
+ if s.path != "" {
+ s.logI("using FS storage")
+ s.logI("storage path: %s", s.path)
+ } else {
+ s.logI("using MEM storage")
+ }
return s
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go
deleted file mode 100644
index 25bf2b29f..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/testutil_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
-// All rights reserved.
-//
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package leveldb
-
-import (
- . "github.com/onsi/gomega"
-
- "github.com/syndtr/goleveldb/leveldb/iterator"
- "github.com/syndtr/goleveldb/leveldb/opt"
- "github.com/syndtr/goleveldb/leveldb/testutil"
- "github.com/syndtr/goleveldb/leveldb/util"
-)
-
-type testingDB struct {
- *DB
- ro *opt.ReadOptions
- wo *opt.WriteOptions
- stor *testutil.Storage
-}
-
-func (t *testingDB) TestPut(key []byte, value []byte) error {
- return t.Put(key, value, t.wo)
-}
-
-func (t *testingDB) TestDelete(key []byte) error {
- return t.Delete(key, t.wo)
-}
-
-func (t *testingDB) TestGet(key []byte) (value []byte, err error) {
- return t.Get(key, t.ro)
-}
-
-func (t *testingDB) TestHas(key []byte) (ret bool, err error) {
- return t.Has(key, t.ro)
-}
-
-func (t *testingDB) TestNewIterator(slice *util.Range) iterator.Iterator {
- return t.NewIterator(slice, t.ro)
-}
-
-func (t *testingDB) TestClose() {
- err := t.Close()
- ExpectWithOffset(1, err).NotTo(HaveOccurred())
- err = t.stor.Close()
- ExpectWithOffset(1, err).NotTo(HaveOccurred())
-}
-
-func newTestingDB(o *opt.Options, ro *opt.ReadOptions, wo *opt.WriteOptions) *testingDB {
- stor := testutil.NewStorage()
- db, err := Open(stor, o)
- // FIXME: This may be called from outside It, which may cause panic.
- Expect(err).NotTo(HaveOccurred())
- return &testingDB{
- DB: db,
- ro: ro,
- wo: wo,
- stor: stor,
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util.go
index 1a5bf71a3..3b663d1cc 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util.go
@@ -72,20 +72,20 @@ func maxInt(a, b int) int {
return b
}
-type files []storage.File
+type fdSorter []storage.FileDesc
-func (p files) Len() int {
+func (p fdSorter) Len() int {
return len(p)
}
-func (p files) Less(i, j int) bool {
- return p[i].Num() < p[j].Num()
+func (p fdSorter) Less(i, j int) bool {
+ return p[i].Num < p[j].Num
}
-func (p files) Swap(i, j int) {
+func (p fdSorter) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
-func (p files) sort() {
- sort.Sort(p)
+func sortFds(fds []storage.FileDesc) {
+ sort.Sort(fdSorter(fds))
}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go
index 2b8453d75..2f3db974a 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_pool.go
@@ -201,6 +201,7 @@ func (p *BufferPool) String() string {
func (p *BufferPool) drain() {
ticker := time.NewTicker(2 * time.Second)
+ defer ticker.Stop()
for {
select {
case <-ticker.C:
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_test.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_test.go
deleted file mode 100644
index 87d96739c..000000000
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/buffer_test.go
+++ /dev/null
@@ -1,369 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package util
-
-import (
- "bytes"
- "io"
- "math/rand"
- "runtime"
- "testing"
-)
-
-const N = 10000 // make this bigger for a larger (and slower) test
-var data string // test data for write tests
-var testBytes []byte // test data; same as data but as a slice.
-
-func init() {
- testBytes = make([]byte, N)
- for i := 0; i < N; i++ {
- testBytes[i] = 'a' + byte(i%26)
- }
- data = string(testBytes)
-}
-
-// Verify that contents of buf match the string s.
-func check(t *testing.T, testname string, buf *Buffer, s string) {
- bytes := buf.Bytes()
- str := buf.String()
- if buf.Len() != len(bytes) {
- t.Errorf("%s: buf.Len() == %d, len(buf.Bytes()) == %d", testname, buf.Len(), len(bytes))
- }
-
- if buf.Len() != len(str) {
- t.Errorf("%s: buf.Len() == %d, len(buf.String()) == %d", testname, buf.Len(), len(str))
- }
-
- if buf.Len() != len(s) {
- t.Errorf("%s: buf.Len() == %d, len(s) == %d", testname, buf.Len(), len(s))
- }
-
- if string(bytes) != s {
- t.Errorf("%s: string(buf.Bytes()) == %q, s == %q", testname, string(bytes), s)
- }
-}
-
-// Fill buf through n writes of byte slice fub.
-// The initial contents of buf corresponds to the string s;
-// the result is the final contents of buf returned as a string.
-func fillBytes(t *testing.T, testname string, buf *Buffer, s string, n int, fub []byte) string {
- check(t, testname+" (fill 1)", buf, s)
- for ; n > 0; n-- {
- m, err := buf.Write(fub)
- if m != len(fub) {
- t.Errorf(testname+" (fill 2): m == %d, expected %d", m, len(fub))
- }
- if err != nil {
- t.Errorf(testname+" (fill 3): err should always be nil, found err == %s", err)
- }
- s += string(fub)
- check(t, testname+" (fill 4)", buf, s)
- }
- return s
-}
-
-func TestNewBuffer(t *testing.T) {
- buf := NewBuffer(testBytes)
- check(t, "NewBuffer", buf, data)
-}
-
-// Empty buf through repeated reads into fub.
-// The initial contents of buf corresponds to the string s.
-func empty(t *testing.T, testname string, buf *Buffer, s string, fub []byte) {
- check(t, testname+" (empty 1)", buf, s)
-
- for {
- n, err := buf.Read(fub)
- if n == 0 {
- break
- }
- if err != nil {
- t.Errorf(testname+" (empty 2): err should always be nil, found err == %s", err)
- }
- s = s[n:]
- check(t, testname+" (empty 3)", buf, s)
- }
-
- check(t, testname+" (empty 4)", buf, "")
-}
-
-func TestBasicOperations(t *testing.T) {
- var buf Buffer
-
- for i := 0; i < 5; i++ {
- check(t, "TestBasicOperations (1)", &buf, "")
-
- buf.Reset()
- check(t, "TestBasicOperations (2)", &buf, "")
-
- buf.Truncate(0)
- check(t, "TestBasicOperations (3)", &buf, "")
-
- n, err := buf.Write([]byte(data[0:1]))
- if n != 1 {
- t.Errorf("wrote 1 byte, but n == %d", n)
- }
- if err != nil {
- t.Errorf("err should always be nil, but err == %s", err)
- }
- check(t, "TestBasicOperations (4)", &buf, "a")
-
- buf.WriteByte(data[1])
- check(t, "TestBasicOperations (5)", &buf, "ab")
-
- n, err = buf.Write([]byte(data[2:26]))
- if n != 24 {
- t.Errorf("wrote 25 bytes, but n == %d", n)
- }
- check(t, "TestBasicOperations (6)", &buf, string(data[0:26]))
-
- buf.Truncate(26)
- check(t, "TestBasicOperations (7)", &buf, string(data[0:26]))
-
- buf.Truncate(20)
- check(t, "TestBasicOperations (8)", &buf, string(data[0:20]))
-
- empty(t, "TestBasicOperations (9)", &buf, string(data[0:20]), make([]byte, 5))
- empty(t, "TestBasicOperations (10)", &buf, "", make([]byte, 100))
-
- buf.WriteByte(data[1])
- c, err := buf.ReadByte()
- if err != nil {
- t.Error("ReadByte unexpected eof")
- }
- if c != data[1] {
- t.Errorf("ReadByte wrong value c=%v", c)
- }
- c, err = buf.ReadByte()
- if err == nil {
- t.Error("ReadByte unexpected not eof")
- }
- }
-}
-
-func TestLargeByteWrites(t *testing.T) {
- var buf Buffer
- limit := 30
- if testing.Short() {
- limit = 9
- }
- for i := 3; i < limit; i += 3 {
- s := fillBytes(t, "TestLargeWrites (1)", &buf, "", 5, testBytes)
- empty(t, "TestLargeByteWrites (2)", &buf, s, make([]byte, len(data)/i))
- }
- check(t, "TestLargeByteWrites (3)", &buf, "")
-}
-
-func TestLargeByteReads(t *testing.T) {
- var buf Buffer
- for i := 3; i < 30; i += 3 {
- s := fillBytes(t, "TestLargeReads (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
- empty(t, "TestLargeReads (2)", &buf, s, make([]byte, len(data)))
- }
- check(t, "TestLargeByteReads (3)", &buf, "")
-}
-
-func TestMixedReadsAndWrites(t *testing.T) {
- var buf Buffer
- s := ""
- for i := 0; i < 50; i++ {
- wlen := rand.Intn(len(data))
- s = fillBytes(t, "TestMixedReadsAndWrites (1)", &buf, s, 1, testBytes[0:wlen])
- rlen := rand.Intn(len(data))
- fub := make([]byte, rlen)
- n, _ := buf.Read(fub)
- s = s[n:]
- }
- empty(t, "TestMixedReadsAndWrites (2)", &buf, s, make([]byte, buf.Len()))
-}
-
-func TestNil(t *testing.T) {
- var b *Buffer
- if b.String() != "<nil>" {
- t.Errorf("expected <nil>; got %q", b.String())
- }
-}
-
-func TestReadFrom(t *testing.T) {
- var buf Buffer
- for i := 3; i < 30; i += 3 {
- s := fillBytes(t, "TestReadFrom (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
- var b Buffer
- b.ReadFrom(&buf)
- empty(t, "TestReadFrom (2)", &b, s, make([]byte, len(data)))
- }
-}
-
-func TestWriteTo(t *testing.T) {
- var buf Buffer
- for i := 3; i < 30; i += 3 {
- s := fillBytes(t, "TestWriteTo (1)", &buf, "", 5, testBytes[0:len(testBytes)/i])
- var b Buffer
- buf.WriteTo(&b)
- empty(t, "TestWriteTo (2)", &b, s, make([]byte, len(data)))
- }
-}
-
-func TestNext(t *testing.T) {
- b := []byte{0, 1, 2, 3, 4}
- tmp := make([]byte, 5)
- for i := 0; i <= 5; i++ {
- for j := i; j <= 5; j++ {
- for k := 0; k <= 6; k++ {
- // 0 <= i <= j <= 5; 0 <= k <= 6
- // Check that if we start with a buffer
- // of length j at offset i and ask for
- // Next(k), we get the right bytes.
- buf := NewBuffer(b[0:j])
- n, _ := buf.Read(tmp[0:i])
- if n != i {
- t.Fatalf("Read %d returned %d", i, n)
- }
- bb := buf.Next(k)
- want := k
- if want > j-i {
- want = j - i
- }
- if len(bb) != want {
- t.Fatalf("in %d,%d: len(Next(%d)) == %d", i, j, k, len(bb))
- }
- for l, v := range bb {
- if v != byte(l+i) {
- t.Fatalf("in %d,%d: Next(%d)[%d] = %d, want %d", i, j, k, l, v, l+i)
- }
- }
- }
- }
- }
-}
-
-var readBytesTests = []struct {
- buffer string
- delim byte
- expected []string
- err error
-}{
- {"", 0, []string{""}, io.EOF},
- {"a\x00", 0, []string{"a\x00"}, nil},
- {"abbbaaaba", 'b', []string{"ab", "b", "b", "aaab"}, nil},
- {"hello\x01world", 1, []string{"hello\x01"}, nil},
- {"foo\nbar", 0, []string{"foo\nbar"}, io.EOF},
- {"alpha\nbeta\ngamma\n", '\n', []string{"alpha\n", "beta\n", "gamma\n"}, nil},
- {"alpha\nbeta\ngamma", '\n', []string{"alpha\n", "beta\n", "gamma"}, io.EOF},
-}
-
-func TestReadBytes(t *testing.T) {
- for _, test := range readBytesTests {
- buf := NewBuffer([]byte(test.buffer))
- var err error
- for _, expected := range test.expected {
- var bytes []byte
- bytes, err = buf.ReadBytes(test.delim)
- if string(bytes) != expected {
- t.Errorf("expected %q, got %q", expected, bytes)
- }
- if err != nil {
- break
- }
- }
- if err != test.err {
- t.Errorf("expected error %v, got %v", test.err, err)
- }
- }
-}
-
-func TestGrow(t *testing.T) {
- x := []byte{'x'}
- y := []byte{'y'}
- tmp := make([]byte, 72)
- for _, startLen := range []int{0, 100, 1000, 10000, 100000} {
- xBytes := bytes.Repeat(x, startLen)
- for _, growLen := range []int{0, 100, 1000, 10000, 100000} {
- buf := NewBuffer(xBytes)
- // If we read, this affects buf.off, which is good to test.
- readBytes, _ := buf.Read(tmp)
- buf.Grow(growLen)
- yBytes := bytes.Repeat(y, growLen)
- // Check no allocation occurs in write, as long as we're single-threaded.
- var m1, m2 runtime.MemStats
- runtime.ReadMemStats(&m1)
- buf.Write(yBytes)
- runtime.ReadMemStats(&m2)
- if runtime.GOMAXPROCS(-1) == 1 && m1.Mallocs != m2.Mallocs {
- t.Errorf("allocation occurred during write")
- }
- // Check that buffer has correct data.
- if !bytes.Equal(buf.Bytes()[0:startLen-readBytes], xBytes[readBytes:]) {
- t.Errorf("bad initial data at %d %d", startLen, growLen)
- }
- if !bytes.Equal(buf.Bytes()[startLen-readBytes:startLen-readBytes+growLen], yBytes) {
- t.Errorf("bad written data at %d %d", startLen, growLen)
- }
- }
- }
-}
-
-// Was a bug: used to give EOF reading empty slice at EOF.
-func TestReadEmptyAtEOF(t *testing.T) {
- b := new(Buffer)
- slice := make([]byte, 0)
- n, err := b.Read(slice)
- if err != nil {
- t.Errorf("read error: %v", err)
- }
- if n != 0 {
- t.Errorf("wrong count; got %d want 0", n)
- }
-}
-
-// Tests that we occasionally compact. Issue 5154.
-func TestBufferGrowth(t *testing.T) {
- var b Buffer
- buf := make([]byte, 1024)
- b.Write(buf[0:1])
- var cap0 int
- for i := 0; i < 5<<10; i++ {
- b.Write(buf)
- b.Read(buf)
- if i == 0 {
- cap0 = cap(b.buf)
- }
- }
- cap1 := cap(b.buf)
- // (*Buffer).grow allows for 2x capacity slop before sliding,
- // so set our error threshold at 3x.
- if cap1 > cap0*3 {
- t.Errorf("buffer cap = %d; too big (grew from %d)", cap1, cap0)
- }
-}
-
-// From Issue 5154.
-func BenchmarkBufferNotEmptyWriteRead(b *testing.B) {
- buf := make([]byte, 1024)
- for i := 0; i < b.N; i++ {
- var b Buffer
- b.Write(buf[0:1])
- for i := 0; i < 5<<10; i++ {
- b.Write(buf)
- b.Read(buf)
- }
- }
-}
-
-// Check that we don't compact too often. From Issue 5154.
-func BenchmarkBufferFullSmallReads(b *testing.B) {
- buf := make([]byte, 1024)
- for i := 0; i < b.N; i++ {
- var b Buffer
- b.Write(buf)
- for b.Len()+20 < cap(b.buf) {
- b.Write(buf[:10])
- }
- for i := 0; i < 5<<10; i++ {
- b.Read(buf[:1])
- b.Write(buf[:1])
- }
- }
-}
diff --git a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/version.go b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/version.go
index 88a52f53e..50870ed83 100644
--- a/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/version.go
+++ b/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/version.go
@@ -7,6 +7,7 @@
package leveldb
import (
+ "fmt"
"sync/atomic"
"unsafe"
@@ -23,7 +24,7 @@ type tSet struct {
type version struct {
s *session
- tables []tFiles
+ levels []tFiles
// Level that should be compacted next and its compaction score.
// Score < 1 means compaction is not strictly needed. These fields
@@ -39,7 +40,7 @@ type version struct {
}
func newVersion(s *session) *version {
- return &version{s: s, tables: make([]tFiles, s.o.GetNumLevel())}
+ return &version{s: s}
}
func (v *version) releaseNB() {
@@ -51,18 +52,18 @@ func (v *version) releaseNB() {
panic("negative version ref")
}
- tables := make(map[uint64]bool)
- for _, tt := range v.next.tables {
+ nextTables := make(map[int64]bool)
+ for _, tt := range v.next.levels {
for _, t := range tt {
- num := t.file.Num()
- tables[num] = true
+ num := t.fd.Num
+ nextTables[num] = true
}
}
- for _, tt := range v.tables {
+ for _, tt := range v.levels {
for _, t := range tt {
- num := t.file.Num()
- if _, ok := tables[num]; !ok {
+ num := t.fd.Num
+ if _, ok := nextTables[num]; !ok {
v.s.tops.remove(t)
}
}
@@ -78,11 +79,26 @@ func (v *version) release() {
v.s.vmu.Unlock()
}
-func (v *version) walkOverlapping(ikey iKey, f func(level int, t *tFile) bool, lf func(level int) bool) {
+func (v *version) walkOverlapping(aux tFiles, ikey iKey, f func(level int, t *tFile) bool, lf func(level int) bool) {
ukey := ikey.ukey()
+ // Aux level.
+ if aux != nil {
+ for _, t := range aux {
+ if t.overlaps(v.s.icmp, ukey, ukey) {
+ if !f(-1, t) {
+ return
+ }
+ }
+ }
+
+ if lf != nil && !lf(-1) {
+ return
+ }
+ }
+
// Walk tables level-by-level.
- for level, tables := range v.tables {
+ for level, tables := range v.levels {
if len(tables) == 0 {
continue
}
@@ -114,7 +130,7 @@ func (v *version) walkOverlapping(ikey iKey, f func(level int, t *tFile) bool, l
}
}
-func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byte, tcomp bool, err error) {
+func (v *version) get(aux tFiles, ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byte, tcomp bool, err error) {
ukey := ikey.ukey()
var (
@@ -130,10 +146,10 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
err = ErrNotFound
- // Since entries never hope across level, finding key/value
+ // Since entries never hop across level, finding key/value
// in smaller level make later levels irrelevant.
- v.walkOverlapping(ikey, func(level int, t *tFile) bool {
- if !tseek {
+ v.walkOverlapping(aux, ikey, func(level int, t *tFile) bool {
+ if level >= 0 && !tseek {
if tset == nil {
tset = &tSet{level, t}
} else {
@@ -150,6 +166,7 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
} else {
fikey, fval, ferr = v.s.tops.find(t, ikey, ro)
}
+
switch ferr {
case nil:
case ErrNotFound:
@@ -161,7 +178,8 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
if fukey, fseq, fkt, fkerr := parseIkey(fikey); fkerr == nil {
if v.s.icmp.uCompare(ukey, fukey) == 0 {
- if level == 0 {
+ // Level <= 0 may overlaps each-other.
+ if level <= 0 {
if fseq >= zseq {
zfound = true
zseq = fseq
@@ -212,7 +230,7 @@ func (v *version) get(ikey iKey, ro *opt.ReadOptions, noValue bool) (value []byt
func (v *version) sampleSeek(ikey iKey) (tcomp bool) {
var tset *tSet
- v.walkOverlapping(ikey, func(level int, t *tFile) bool {
+ v.walkOverlapping(nil, ikey, func(level int, t *tFile) bool {
if tset == nil {
tset = &tSet{level, t}
return true
@@ -228,27 +246,22 @@ func (v *version) sampleSeek(ikey iKey) (tcomp bool) {
}
func (v *version) getIterators(slice *util.Range, ro *opt.ReadOptions) (its []iterator.Iterator) {
- // Merge all level zero files together since they may overlap
- for _, t := range v.tables[0] {
- it := v.s.tops.newIterator(t, slice, ro)
- its = append(its, it)
- }
-
strict := opt.GetStrict(v.s.o.Options, ro, opt.StrictReader)
- for _, tables := range v.tables[1:] {
- if len(tables) == 0 {
- continue
+ for level, tables := range v.levels {
+ if level == 0 {
+ // Merge all level zero files together since they may overlap.
+ for _, t := range tables {
+ its = append(its, v.s.tops.newIterator(t, slice, ro))
+ }
+ } else if len(tables) != 0 {
+ its = append(its, iterator.NewIndexedIterator(tables.newIndexIterator(v.s.tops, v.s.icmp, slice, ro), strict))
}
-
- it := iterator.NewIndexedIterator(tables.newIndexIterator(v.s.tops, v.s.icmp, slice, ro), strict)
- its = append(its, it)
}
-
return
}
func (v *version) newStaging() *versionStaging {
- return &versionStaging{base: v, tables: make([]tablesScratch, v.s.o.GetNumLevel())}
+ return &versionStaging{base: v}
}
// Spawn a new version based on this version.
@@ -259,23 +272,26 @@ func (v *version) spawn(r *sessionRecord) *version {
}
func (v *version) fillRecord(r *sessionRecord) {
- for level, ts := range v.tables {
- for _, t := range ts {
+ for level, tables := range v.levels {
+ for _, t := range tables {
r.addTableFile(level, t)
}
}
}
func (v *version) tLen(level int) int {
- return len(v.tables[level])
+ if level < len(v.levels) {
+ return len(v.levels[level])
+ }
+ return 0
}
func (v *version) offsetOf(ikey iKey) (n uint64, err error) {
- for level, tables := range v.tables {
+ for level, tables := range v.levels {
for _, t := range tables {
if v.s.icmp.Compare(t.imax, ikey) <= 0 {
// Entire file is before "ikey", so just add the file size
- n += t.size
+ n += uint64(t.size)
} else if v.s.icmp.Compare(t.imin, ikey) > 0 {
// Entire file is after "ikey", so ignore
if level > 0 {
@@ -300,37 +316,50 @@ func (v *version) offsetOf(ikey iKey) (n uint64, err error) {
return
}
-func (v *version) pickLevel(umin, umax []byte) (level int) {
- if !v.tables[0].overlaps(v.s.icmp, umin, umax, true) {
- var overlaps tFiles
- maxLevel := v.s.o.GetMaxMemCompationLevel()
- for ; level < maxLevel; level++ {
- if v.tables[level+1].overlaps(v.s.icmp, umin, umax, false) {
- break
- }
- overlaps = v.tables[level+2].getOverlaps(overlaps, v.s.icmp, umin, umax, false)
- if overlaps.size() > uint64(v.s.o.GetCompactionGPOverlaps(level)) {
- break
+func (v *version) pickMemdbLevel(umin, umax []byte, maxLevel int) (level int) {
+ if maxLevel > 0 {
+ if len(v.levels) == 0 {
+ return maxLevel
+ }
+ if !v.levels[0].overlaps(v.s.icmp, umin, umax, true) {
+ var overlaps tFiles
+ for ; level < maxLevel; level++ {
+ if pLevel := level + 1; pLevel >= len(v.levels) {
+ return maxLevel
+ } else if v.levels[pLevel].overlaps(v.s.icmp, umin, umax, false) {
+ break
+ }
+ if gpLevel := level + 2; gpLevel < len(v.levels) {
+ overlaps = v.levels[gpLevel].getOverlaps(overlaps, v.s.icmp, umin, umax, false)
+ if overlaps.size() > int64(v.s.o.GetCompactionGPOverlaps(level)) {
+ break
+ }
+ }
}
}
}
-
return
}
func (v *version) computeCompaction() {
// Precomputed best level for next compaction
- var bestLevel int = -1
- var bestScore float64 = -1
+ bestLevel := int(-1)
+ bestScore := float64(-1)
- for level, tables := range v.tables {
+ statFiles := make([]int, len(v.levels))
+ statSizes := make([]string, len(v.levels))
+ statScore := make([]string, len(v.levels))
+ statTotSize := int64(0)
+
+ for level, tables := range v.levels {
var score float64
+ size := tables.size()
if level == 0 {
// We treat level-0 specially by bounding the number of files
// instead of number of bytes for two reasons:
//
// (1) With larger write-buffer sizes, it is nice not to do too
- // many level-0 compactions.
+ // many level-0 compaction.
//
// (2) The files in level-0 are merged on every read and
// therefore we wish to avoid too many files when the individual
@@ -339,17 +368,24 @@ func (v *version) computeCompaction() {
// overwrites/deletions).
score = float64(len(tables)) / float64(v.s.o.GetCompactionL0Trigger())
} else {
- score = float64(tables.size()) / float64(v.s.o.GetCompactionTotalSize(level))
+ score = float64(size) / float64(v.s.o.GetCompactionTotalSize(level))
}
if score > bestScore {
bestLevel = level
bestScore = score
}
+
+ statFiles[level] = len(tables)
+ statSizes[level] = shortenb(int(size))
+ statScore[level] = fmt.Sprintf("%.2f", score)
+ statTotSize += size
}
v.cLevel = bestLevel
v.cScore = bestScore
+
+ v.s.logf("version@stat F·%v S·%s%v Sc·%v", statFiles, shortenb(int(statTotSize)), statSizes, statScore)
}
func (v *version) needCompaction() bool {
@@ -357,43 +393,48 @@ func (v *version) needCompaction() bool {
}
type tablesScratch struct {
- added map[uint64]atRecord
- deleted map[uint64]struct{}
+ added map[int64]atRecord
+ deleted map[int64]struct{}
}
type versionStaging struct {
base *version
- tables []tablesScratch
+ levels []tablesScratch
+}
+
+func (p *versionStaging) getScratch(level int) *tablesScratch {
+ if level >= len(p.levels) {
+ newLevels := make([]tablesScratch, level+1)
+ copy(newLevels, p.levels)
+ p.levels = newLevels
+ }
+ return &(p.levels[level])
}
func (p *versionStaging) commit(r *sessionRecord) {
// Deleted tables.
for _, r := range r.deletedTables {
- tm := &(p.tables[r.level])
-
- if len(p.base.tables[r.level]) > 0 {
- if tm.deleted == nil {
- tm.deleted = make(map[uint64]struct{})
+ scratch := p.getScratch(r.level)
+ if r.level < len(p.base.levels) && len(p.base.levels[r.level]) > 0 {
+ if scratch.deleted == nil {
+ scratch.deleted = make(map[int64]struct{})
}
- tm.deleted[r.num] = struct{}{}
+ scratch.deleted[r.num] = struct{}{}
}
-
- if tm.added != nil {
- delete(tm.added, r.num)
+ if scratch.added != nil {
+ delete(scratch.added, r.num)
}
}
// New tables.
for _, r := range r.addedTables {
- tm := &(p.tables[r.level])
-
- if tm.added == nil {
- tm.added = make(map[uint64]atRecord)
+ scratch := p.getScratch(r.level)
+ if scratch.added == nil {
+ scratch.added = make(map[int64]atRecord)
}
- tm.added[r.num] = r
-
- if tm.deleted != nil {
- delete(tm.deleted, r.num)
+ scratch.added[r.num] = r
+ if scratch.deleted != nil {
+ delete(scratch.deleted, r.num)
}
}
}
@@ -401,40 +442,63 @@ func (p *versionStaging) commit(r *sessionRecord) {
func (p *versionStaging) finish() *version {
// Build new version.
nv := newVersion(p.base.s)
- for level, tm := range p.tables {
- btables := p.base.tables[level]
-
- n := len(btables) + len(tm.added) - len(tm.deleted)
- if n < 0 {
- n = 0
+ numLevel := len(p.levels)
+ if len(p.base.levels) > numLevel {
+ numLevel = len(p.base.levels)
+ }
+ nv.levels = make([]tFiles, numLevel)
+ for level := 0; level < numLevel; level++ {
+ var baseTabels tFiles
+ if level < len(p.base.levels) {
+ baseTabels = p.base.levels[level]
}
- nt := make(tFiles, 0, n)
- // Base tables.
- for _, t := range btables {
- if _, ok := tm.deleted[t.file.Num()]; ok {
- continue
+ if level < len(p.levels) {
+ scratch := p.levels[level]
+
+ var nt tFiles
+ // Prealloc list if possible.
+ if n := len(baseTabels) + len(scratch.added) - len(scratch.deleted); n > 0 {
+ nt = make(tFiles, 0, n)
}
- if _, ok := tm.added[t.file.Num()]; ok {
- continue
+
+ // Base tables.
+ for _, t := range baseTabels {
+ if _, ok := scratch.deleted[t.fd.Num]; ok {
+ continue
+ }
+ if _, ok := scratch.added[t.fd.Num]; ok {
+ continue
+ }
+ nt = append(nt, t)
}
- nt = append(nt, t)
- }
- // New tables.
- for _, r := range tm.added {
- nt = append(nt, p.base.s.tableFileFromRecord(r))
- }
+ // New tables.
+ for _, r := range scratch.added {
+ nt = append(nt, tableFileFromRecord(r))
+ }
- // Sort tables.
- if level == 0 {
- nt.sortByNum()
+ if len(nt) != 0 {
+ // Sort tables.
+ if level == 0 {
+ nt.sortByNum()
+ } else {
+ nt.sortByKey(p.base.s.icmp)
+ }
+
+ nv.levels[level] = nt
+ }
} else {
- nt.sortByKey(p.base.s.icmp)
+ nv.levels[level] = baseTabels
}
- nv.tables[level] = nt
}
+ // Trim levels.
+ n := len(nv.levels)
+ for ; n > 0 && nv.levels[n-1] == nil; n-- {
+ }
+ nv.levels = nv.levels[:n]
+
// Compute compaction score for new version.
nv.computeCompaction()