vendor: update leveldb

This commit is contained in:
Julian Yap 2018-12-12 23:42:11 -08:00
parent 4e283a442b
commit 3dfc821701
26 changed files with 490 additions and 172 deletions

View file

@ -1,12 +0,0 @@
language: go
go:
- 1.4
- 1.5
- 1.6
- 1.7
- tip
script:
- go test -timeout 1h ./...
- go test -timeout 30m -race -run "TestDB_(Concurrent|GoleveldbIssue74)" ./leveldb

View file

@ -10,13 +10,15 @@ Installation
Requirements Requirements
----------- -----------
* Need at least `go1.4` or newer. * Need at least `go1.5` or newer.
Usage Usage
----------- -----------
Create or open a database: Create or open a database:
```go ```go
// The returned DB instance is safe for concurrent use. Which mean that all
// DB's methods may be called concurrently from multiple goroutine.
db, err := leveldb.OpenFile("path/to/db", nil) db, err := leveldb.OpenFile("path/to/db", nil)
... ...
defer db.Close() defer db.Close()

View file

@ -331,7 +331,6 @@ func (r *Cache) delete(n *Node) bool {
return deleted return deleted
} }
} }
return false
} }
// Nodes returns number of 'cache node' in the map. // Nodes returns number of 'cache node' in the map.

View file

@ -29,7 +29,7 @@ func (bytesComparer) Separator(dst, a, b []byte) []byte {
// Do not shorten if one string is a prefix of the other // Do not shorten if one string is a prefix of the other
} else if c := a[i]; c < 0xff && c+1 < b[i] { } else if c := a[i]; c < 0xff && c+1 < b[i] {
dst = append(dst, a[:i+1]...) dst = append(dst, a[:i+1]...)
dst[i]++ dst[len(dst)-1]++
return dst return dst
} }
return nil return nil
@ -39,7 +39,7 @@ func (bytesComparer) Successor(dst, b []byte) []byte {
for i, c := range b { for i, c := range b {
if c != 0xff { if c != 0xff {
dst = append(dst, b[:i+1]...) dst = append(dst, b[:i+1]...)
dst[i]++ dst[len(dst)-1]++
return dst return dst
} }
} }

View file

@ -36,7 +36,7 @@ type Comparer interface {
// by any users of this package. // by any users of this package.
Name() string Name() string
// Bellow are advanced functions used used to reduce the space requirements // Bellow are advanced functions used to reduce the space requirements
// for internal data structures such as index blocks. // for internal data structures such as index blocks.
// Separator appends a sequence of bytes x to dst such that a <= x && x < b, // Separator appends a sequence of bytes x to dst such that a <= x && x < b,

View file

@ -32,6 +32,12 @@ type DB struct {
// Need 64-bit alignment. // Need 64-bit alignment.
seq uint64 seq uint64
// Stats. Need 64-bit alignment.
cWriteDelay int64 // The cumulative duration of write delays
cWriteDelayN int32 // The cumulative number of write delays
inWritePaused int32 // The indicator whether write operation is paused by compaction
aliveSnaps, aliveIters int32
// Session. // Session.
s *session s *session
@ -49,9 +55,6 @@ type DB struct {
snapsMu sync.Mutex snapsMu sync.Mutex
snapsList *list.List snapsList *list.List
// Stats.
aliveSnaps, aliveIters int32
// Write. // Write.
batchPool sync.Pool batchPool sync.Pool
writeMergeC chan writeMerge writeMergeC chan writeMerge
@ -179,7 +182,7 @@ func Open(stor storage.Storage, o *opt.Options) (db *DB, err error) {
err = s.recover() err = s.recover()
if err != nil { if err != nil {
if !os.IsNotExist(err) || s.o.GetErrorIfMissing() { if !os.IsNotExist(err) || s.o.GetErrorIfMissing() || s.o.GetReadOnly() {
return return
} }
err = s.create() err = s.create()
@ -321,7 +324,7 @@ func recoverTable(s *session, o *opt.Options) error {
} }
} }
err = iter.Error() err = iter.Error()
if err != nil { if err != nil && !errors.IsCorrupted(err) {
return return
} }
err = tw.Close() err = tw.Close()
@ -392,7 +395,7 @@ func recoverTable(s *session, o *opt.Options) error {
} }
imax = append(imax[:0], key...) imax = append(imax[:0], key...)
} }
if err := iter.Error(); err != nil { if err := iter.Error(); err != nil && !errors.IsCorrupted(err) {
iter.Release() iter.Release()
return err return err
} }
@ -844,7 +847,7 @@ func (db *DB) Get(key []byte, ro *opt.ReadOptions) (value []byte, err error) {
// Has returns true if the DB does contains the given key. // Has returns true if the DB does contains the given key.
// //
// It is safe to modify the contents of the argument after Get returns. // It is safe to modify the contents of the argument after Has returns.
func (db *DB) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) { func (db *DB) Has(key []byte, ro *opt.ReadOptions) (ret bool, err error) {
err = db.ok() err = db.ok()
if err != nil { if err != nil {
@ -904,6 +907,10 @@ func (db *DB) GetSnapshot() (*Snapshot, error) {
// Returns the number of files at level 'n'. // Returns the number of files at level 'n'.
// leveldb.stats // leveldb.stats
// Returns statistics of the underlying DB. // Returns statistics of the underlying DB.
// leveldb.iostats
// Returns statistics of effective disk read and write.
// leveldb.writedelay
// Returns cumulative write delay caused by compaction.
// leveldb.sstables // leveldb.sstables
// Returns sstables list for each level. // Returns sstables list for each level.
// leveldb.blockpool // leveldb.blockpool
@ -955,6 +962,14 @@ func (db *DB) GetProperty(name string) (value string, err error) {
level, len(tables), float64(tables.size())/1048576.0, duration.Seconds(), level, len(tables), float64(tables.size())/1048576.0, duration.Seconds(),
float64(read)/1048576.0, float64(write)/1048576.0) float64(read)/1048576.0, float64(write)/1048576.0)
} }
case p == "iostats":
value = fmt.Sprintf("Read(MB):%.5f Write(MB):%.5f",
float64(db.s.stor.reads())/1048576.0,
float64(db.s.stor.writes())/1048576.0)
case p == "writedelay":
writeDelayN, writeDelay := atomic.LoadInt32(&db.cWriteDelayN), time.Duration(atomic.LoadInt64(&db.cWriteDelay))
paused := atomic.LoadInt32(&db.inWritePaused) == 1
value = fmt.Sprintf("DelayN:%d Delay:%s Paused:%t", writeDelayN, writeDelay, paused)
case p == "sstables": case p == "sstables":
for level, tables := range v.levels { for level, tables := range v.levels {
value += fmt.Sprintf("--- level %d ---\n", level) value += fmt.Sprintf("--- level %d ---\n", level)
@ -983,6 +998,75 @@ func (db *DB) GetProperty(name string) (value string, err error) {
return return
} }
// DBStats is database statistics.
type DBStats struct {
WriteDelayCount int32
WriteDelayDuration time.Duration
WritePaused bool
AliveSnapshots int32
AliveIterators int32
IOWrite uint64
IORead uint64
BlockCacheSize int
OpenedTablesCount int
LevelSizes []int64
LevelTablesCounts []int
LevelRead []int64
LevelWrite []int64
LevelDurations []time.Duration
}
// Stats populates s with database statistics.
func (db *DB) Stats(s *DBStats) error {
err := db.ok()
if err != nil {
return err
}
s.IORead = db.s.stor.reads()
s.IOWrite = db.s.stor.writes()
s.WriteDelayCount = atomic.LoadInt32(&db.cWriteDelayN)
s.WriteDelayDuration = time.Duration(atomic.LoadInt64(&db.cWriteDelay))
s.WritePaused = atomic.LoadInt32(&db.inWritePaused) == 1
s.OpenedTablesCount = db.s.tops.cache.Size()
if db.s.tops.bcache != nil {
s.BlockCacheSize = db.s.tops.bcache.Size()
} else {
s.BlockCacheSize = 0
}
s.AliveIterators = atomic.LoadInt32(&db.aliveIters)
s.AliveSnapshots = atomic.LoadInt32(&db.aliveSnaps)
s.LevelDurations = s.LevelDurations[:0]
s.LevelRead = s.LevelRead[:0]
s.LevelWrite = s.LevelWrite[:0]
s.LevelSizes = s.LevelSizes[:0]
s.LevelTablesCounts = s.LevelTablesCounts[:0]
v := db.s.version()
defer v.release()
for level, tables := range v.levels {
duration, read, write := db.compStats.getStat(level)
if len(tables) == 0 && duration == 0 {
continue
}
s.LevelDurations = append(s.LevelDurations, duration)
s.LevelRead = append(s.LevelRead, read)
s.LevelWrite = append(s.LevelWrite, write)
s.LevelSizes = append(s.LevelSizes, tables.size())
s.LevelTablesCounts = append(s.LevelTablesCounts, len(tables))
}
return nil
}
// SizeOf calculates approximate sizes of the given key ranges. // SizeOf calculates approximate sizes of the given key ranges.
// The length of the returned sizes are equal with the length of the given // The length of the returned sizes are equal with the length of the given
// ranges. The returned sizes measure storage space usage, so if the user // ranges. The returned sizes measure storage space usage, so if the user

View file

@ -289,7 +289,7 @@ func (db *DB) memCompaction() {
close(resumeC) close(resumeC)
resumeC = nil resumeC = nil
case <-db.closeC: case <-db.closeC:
return db.compactionExitTransact()
} }
var ( var (
@ -338,7 +338,7 @@ func (db *DB) memCompaction() {
case <-resumeC: case <-resumeC:
close(resumeC) close(resumeC)
case <-db.closeC: case <-db.closeC:
return db.compactionExitTransact()
} }
} }
@ -640,6 +640,16 @@ func (db *DB) tableNeedCompaction() bool {
return v.needCompaction() return v.needCompaction()
} }
// resumeWrite returns an indicator whether we should resume write operation if enough level0 files are compacted.
func (db *DB) resumeWrite() bool {
v := db.s.version()
defer v.release()
if v.tLen(0) < db.s.o.GetWriteL0PauseTrigger() {
return true
}
return false
}
func (db *DB) pauseCompaction(ch chan<- struct{}) { func (db *DB) pauseCompaction(ch chan<- struct{}) {
select { select {
case ch <- struct{}{}: case ch <- struct{}{}:
@ -653,6 +663,7 @@ type cCmd interface {
} }
type cAuto struct { type cAuto struct {
// Note for table compaction, an non-empty ackC represents it's a compaction waiting command.
ackC chan<- error ackC chan<- error
} }
@ -765,8 +776,10 @@ func (db *DB) mCompaction() {
} }
func (db *DB) tCompaction() { func (db *DB) tCompaction() {
var x cCmd var (
var ackQ []cCmd x cCmd
waitQ []cCmd
)
defer func() { defer func() {
if x := recover(); x != nil { if x := recover(); x != nil {
@ -774,9 +787,9 @@ func (db *DB) tCompaction() {
panic(x) panic(x)
} }
} }
for i := range ackQ { for i := range waitQ {
ackQ[i].ack(ErrClosed) waitQ[i].ack(ErrClosed)
ackQ[i] = nil waitQ[i] = nil
} }
if x != nil { if x != nil {
x.ack(ErrClosed) x.ack(ErrClosed)
@ -795,12 +808,20 @@ func (db *DB) tCompaction() {
return return
default: default:
} }
} else { // Resume write operation as soon as possible.
for i := range ackQ { if len(waitQ) > 0 && db.resumeWrite() {
ackQ[i].ack(nil) for i := range waitQ {
ackQ[i] = nil waitQ[i].ack(nil)
waitQ[i] = nil
} }
ackQ = ackQ[:0] waitQ = waitQ[:0]
}
} else {
for i := range waitQ {
waitQ[i].ack(nil)
waitQ[i] = nil
}
waitQ = waitQ[:0]
select { select {
case x = <-db.tcompCmdC: case x = <-db.tcompCmdC:
case ch := <-db.tcompPauseC: case ch := <-db.tcompPauseC:
@ -813,7 +834,14 @@ func (db *DB) tCompaction() {
if x != nil { if x != nil {
switch cmd := x.(type) { switch cmd := x.(type) {
case cAuto: case cAuto:
ackQ = append(ackQ, x) if cmd.ackC != nil {
// Check the write pause state before caching it.
if db.resumeWrite() {
x.ack(nil)
} else {
waitQ = append(waitQ, x)
}
}
case cRange: case cRange:
x.ack(db.tableRangeCompaction(cmd.level, cmd.min, cmd.max)) x.ack(db.tableRangeCompaction(cmd.level, cmd.min, cmd.max))
default: default:

View file

@ -7,6 +7,7 @@
package leveldb package leveldb
import ( import (
"errors"
"sync/atomic" "sync/atomic"
"time" "time"
@ -15,6 +16,10 @@ import (
"github.com/syndtr/goleveldb/leveldb/storage" "github.com/syndtr/goleveldb/leveldb/storage"
) )
var (
errHasFrozenMem = errors.New("has frozen mem")
)
type memDB struct { type memDB struct {
db *DB db *DB
*memdb.DB *memdb.DB
@ -126,7 +131,7 @@ func (db *DB) newMem(n int) (mem *memDB, err error) {
defer db.memMu.Unlock() defer db.memMu.Unlock()
if db.frozenMem != nil { if db.frozenMem != nil {
panic("still has frozen mem") return nil, errHasFrozenMem
} }
if db.journal == nil { if db.journal == nil {

View file

@ -84,7 +84,7 @@ func (db *DB) checkAndCleanFiles() error {
var mfds []storage.FileDesc var mfds []storage.FileDesc
for num, present := range tmap { for num, present := range tmap {
if !present { if !present {
mfds = append(mfds, storage.FileDesc{storage.TypeTable, num}) mfds = append(mfds, storage.FileDesc{Type: storage.TypeTable, Num: num})
db.logf("db@janitor table missing @%d", num) db.logf("db@janitor table missing @%d", num)
} }
} }

View file

@ -7,6 +7,7 @@
package leveldb package leveldb
import ( import (
"sync/atomic"
"time" "time"
"github.com/syndtr/goleveldb/leveldb/memdb" "github.com/syndtr/goleveldb/leveldb/memdb"
@ -32,15 +33,24 @@ func (db *DB) writeJournal(batches []*Batch, seq uint64, sync bool) error {
} }
func (db *DB) rotateMem(n int, wait bool) (mem *memDB, err error) { func (db *DB) rotateMem(n int, wait bool) (mem *memDB, err error) {
retryLimit := 3
retry:
// Wait for pending memdb compaction. // Wait for pending memdb compaction.
err = db.compTriggerWait(db.mcompCmdC) err = db.compTriggerWait(db.mcompCmdC)
if err != nil { if err != nil {
return return
} }
retryLimit--
// Create new memdb and journal. // Create new memdb and journal.
mem, err = db.newMem(n) mem, err = db.newMem(n)
if err != nil { if err != nil {
if err == errHasFrozenMem {
if retryLimit <= 0 {
panic("BUG: still has frozen memdb")
}
goto retry
}
return return
} }
@ -79,7 +89,11 @@ func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
return false return false
case tLen >= pauseTrigger: case tLen >= pauseTrigger:
delayed = true delayed = true
// Set the write paused flag explicitly.
atomic.StoreInt32(&db.inWritePaused, 1)
err = db.compTriggerWait(db.tcompCmdC) err = db.compTriggerWait(db.tcompCmdC)
// Unset the write paused flag.
atomic.StoreInt32(&db.inWritePaused, 0)
if err != nil { if err != nil {
return false return false
} }
@ -108,6 +122,8 @@ func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
db.writeDelayN++ db.writeDelayN++
} else if db.writeDelayN > 0 { } else if db.writeDelayN > 0 {
db.logf("db@write was delayed N·%d T·%v", db.writeDelayN, db.writeDelay) db.logf("db@write was delayed N·%d T·%v", db.writeDelayN, db.writeDelay)
atomic.AddInt32(&db.cWriteDelayN, int32(db.writeDelayN))
atomic.AddInt64(&db.cWriteDelay, int64(db.writeDelay))
db.writeDelay = 0 db.writeDelay = 0
db.writeDelayN = 0 db.writeDelayN = 0
} }
@ -134,7 +150,7 @@ func (db *DB) unlockWrite(overflow bool, merged int, err error) {
} }
} }
// ourBatch if defined should equal with batch. // ourBatch is batch that we can modify.
func (db *DB) writeLocked(batch, ourBatch *Batch, merge, sync bool) error { func (db *DB) writeLocked(batch, ourBatch *Batch, merge, sync bool) error {
// Try to flush memdb. This method would also trying to throttle writes // Try to flush memdb. This method would also trying to throttle writes
// if it is too fast and compaction cannot catch-up. // if it is too fast and compaction cannot catch-up.
@ -203,6 +219,11 @@ func (db *DB) writeLocked(batch, ourBatch *Batch, merge, sync bool) error {
} }
} }
// Release ourBatch if any.
if ourBatch != nil {
defer db.batchPool.Put(ourBatch)
}
// Seq number. // Seq number.
seq := db.seq + 1 seq := db.seq + 1

View file

@ -8,6 +8,8 @@
// //
// Create or open a database: // Create or open a database:
// //
// // The returned DB instance is safe for concurrent use. Which mean that all
// // DB's methods may be called concurrently from multiple goroutine.
// db, err := leveldb.OpenFile("path/to/db", nil) // db, err := leveldb.OpenFile("path/to/db", nil)
// ... // ...
// defer db.Close() // defer db.Close()

View file

@ -40,11 +40,11 @@ type IteratorSeeker interface {
Seek(key []byte) bool Seek(key []byte) bool
// Next moves the iterator to the next key/value pair. // Next moves the iterator to the next key/value pair.
// It returns whether the iterator is exhausted. // It returns false if the iterator is exhausted.
Next() bool Next() bool
// Prev moves the iterator to the previous key/value pair. // Prev moves the iterator to the previous key/value pair.
// It returns whether the iterator is exhausted. // It returns false if the iterator is exhausted.
Prev() bool Prev() bool
} }
@ -88,7 +88,7 @@ type Iterator interface {
// its contents may change on the next call to any 'seeks method'. // its contents may change on the next call to any 'seeks method'.
Key() []byte Key() []byte
// Value returns the key of the current key/value pair, or nil if done. // Value returns the value of the current key/value pair, or nil if done.
// The caller should not modify the contents of the returned slice, and // The caller should not modify the contents of the returned slice, and
// its contents may change on the next call to any 'seeks method'. // its contents may change on the next call to any 'seeks method'.
Value() []byte Value() []byte

View file

@ -329,7 +329,7 @@ func (p *DB) Delete(key []byte) error {
h := p.nodeData[node+nHeight] h := p.nodeData[node+nHeight]
for i, n := range p.prevNode[:h] { for i, n := range p.prevNode[:h] {
m := n + 4 + i m := n + nNext + i
p.nodeData[m] = p.nodeData[p.nodeData[m]+nNext+i] p.nodeData[m] = p.nodeData[p.nodeData[m]+nNext+i]
} }

View file

@ -158,6 +158,12 @@ type Options struct {
// The default value is 8MiB. // The default value is 8MiB.
BlockCacheCapacity int BlockCacheCapacity int
// BlockCacheEvictRemoved allows enable forced-eviction on cached block belonging
// to removed 'sorted table'.
//
// The default if false.
BlockCacheEvictRemoved bool
// BlockRestartInterval is the number of keys between restart points for // BlockRestartInterval is the number of keys between restart points for
// delta encoding of keys. // delta encoding of keys.
// //
@ -384,6 +390,13 @@ func (o *Options) GetBlockCacheCapacity() int {
return o.BlockCacheCapacity return o.BlockCacheCapacity
} }
func (o *Options) GetBlockCacheEvictRemoved() bool {
if o == nil {
return false
}
return o.BlockCacheEvictRemoved
}
func (o *Options) GetBlockRestartInterval() int { func (o *Options) GetBlockRestartInterval() int {
if o == nil || o.BlockRestartInterval <= 0 { if o == nil || o.BlockRestartInterval <= 0 {
return DefaultBlockRestartInterval return DefaultBlockRestartInterval

View file

@ -42,7 +42,7 @@ type session struct {
stTempFileNum int64 stTempFileNum int64
stSeqNum uint64 // last mem compacted seq; need external synchronization stSeqNum uint64 // last mem compacted seq; need external synchronization
stor storage.Storage stor *iStorage
storLock storage.Locker storLock storage.Locker
o *cachedOptions o *cachedOptions
icmp *iComparer icmp *iComparer
@ -68,7 +68,7 @@ func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) {
return return
} }
s = &session{ s = &session{
stor: stor, stor: newIStorage(stor),
storLock: storLock, storLock: storLock,
fileRef: make(map[int64]int), fileRef: make(map[int64]int),
} }

View file

@ -36,7 +36,7 @@ func (s *session) logf(format string, v ...interface{}) { s.stor.Log(fmt.Sprintf
func (s *session) newTemp() storage.FileDesc { func (s *session) newTemp() storage.FileDesc {
num := atomic.AddInt64(&s.stTempFileNum, 1) - 1 num := atomic.AddInt64(&s.stTempFileNum, 1) - 1
return storage.FileDesc{storage.TypeTemp, num} return storage.FileDesc{Type: storage.TypeTemp, Num: num}
} }
func (s *session) addFileRef(fd storage.FileDesc, ref int) int { func (s *session) addFileRef(fd storage.FileDesc, ref int) int {
@ -190,7 +190,7 @@ func (s *session) recordCommited(rec *sessionRecord) {
// Create a new manifest file; need external synchronization. // Create a new manifest file; need external synchronization.
func (s *session) newManifest(rec *sessionRecord, v *version) (err error) { func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
fd := storage.FileDesc{storage.TypeManifest, s.allocFileNum()} fd := storage.FileDesc{Type: storage.TypeManifest, Num: s.allocFileNum()}
writer, err := s.stor.Create(fd) writer, err := s.stor.Create(fd)
if err != nil { if err != nil {
return return

63
vendor/github.com/syndtr/goleveldb/leveldb/storage.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
package leveldb
import (
"github.com/syndtr/goleveldb/leveldb/storage"
"sync/atomic"
)
type iStorage struct {
storage.Storage
read uint64
write uint64
}
func (c *iStorage) Open(fd storage.FileDesc) (storage.Reader, error) {
r, err := c.Storage.Open(fd)
return &iStorageReader{r, c}, err
}
func (c *iStorage) Create(fd storage.FileDesc) (storage.Writer, error) {
w, err := c.Storage.Create(fd)
return &iStorageWriter{w, c}, err
}
func (c *iStorage) reads() uint64 {
return atomic.LoadUint64(&c.read)
}
func (c *iStorage) writes() uint64 {
return atomic.LoadUint64(&c.write)
}
// newIStorage returns the given storage wrapped by iStorage.
func newIStorage(s storage.Storage) *iStorage {
return &iStorage{s, 0, 0}
}
type iStorageReader struct {
storage.Reader
c *iStorage
}
func (r *iStorageReader) Read(p []byte) (n int, err error) {
n, err = r.Reader.Read(p)
atomic.AddUint64(&r.c.read, uint64(n))
return n, err
}
func (r *iStorageReader) ReadAt(p []byte, off int64) (n int, err error) {
n, err = r.Reader.ReadAt(p, off)
atomic.AddUint64(&r.c.read, uint64(n))
return n, err
}
type iStorageWriter struct {
storage.Writer
c *iStorage
}
func (w *iStorageWriter) Write(p []byte) (n int, err error) {
n, err = w.Writer.Write(p)
atomic.AddUint64(&w.c.write, uint64(n))
return n, err
}

View file

@ -9,10 +9,12 @@ package storage
import ( import (
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -42,6 +44,30 @@ func (lock *fileStorageLock) Unlock() {
} }
} }
type int64Slice []int64
func (p int64Slice) Len() int { return len(p) }
func (p int64Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p int64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func writeFileSynced(filename string, data []byte, perm os.FileMode) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Sync(); err == nil {
err = err1
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
const logSizeThreshold = 1024 * 1024 // 1 MiB const logSizeThreshold = 1024 * 1024 // 1 MiB
// fileStorage is a file-system backed storage. // fileStorage is a file-system backed storage.
@ -60,7 +86,7 @@ type fileStorage struct {
day int day int
} }
// OpenFile returns a new filesytem-backed storage implementation with the given // OpenFile returns a new filesystem-backed storage implementation with the given
// path. This also acquire a file lock, so any subsequent attempt to open the // path. This also acquire a file lock, so any subsequent attempt to open the
// same path will fail. // same path will fail.
// //
@ -189,7 +215,8 @@ func (fs *fileStorage) doLog(t time.Time, str string) {
// write // write
fs.buf = append(fs.buf, []byte(str)...) fs.buf = append(fs.buf, []byte(str)...)
fs.buf = append(fs.buf, '\n') fs.buf = append(fs.buf, '\n')
fs.logw.Write(fs.buf) n, _ := fs.logw.Write(fs.buf)
fs.logSize += int64(n)
} }
func (fs *fileStorage) Log(str string) { func (fs *fileStorage) Log(str string) {
@ -210,7 +237,46 @@ func (fs *fileStorage) log(str string) {
} }
} }
func (fs *fileStorage) SetMeta(fd FileDesc) (err error) { func (fs *fileStorage) setMeta(fd FileDesc) error {
content := fsGenName(fd) + "\n"
// Check and backup old CURRENT file.
currentPath := filepath.Join(fs.path, "CURRENT")
if _, err := os.Stat(currentPath); err == nil {
b, err := ioutil.ReadFile(currentPath)
if err != nil {
fs.log(fmt.Sprintf("backup CURRENT: %v", err))
return err
}
if string(b) == content {
// Content not changed, do nothing.
return nil
}
if err := writeFileSynced(currentPath+".bak", b, 0644); err != nil {
fs.log(fmt.Sprintf("backup CURRENT: %v", err))
return err
}
} else if !os.IsNotExist(err) {
return err
}
path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num)
if err := writeFileSynced(path, []byte(content), 0644); err != nil {
fs.log(fmt.Sprintf("create CURRENT.%d: %v", fd.Num, err))
return err
}
// Replace CURRENT file.
if err := rename(path, currentPath); err != nil {
fs.log(fmt.Sprintf("rename CURRENT.%d: %v", fd.Num, err))
return err
}
// Sync root directory.
if err := syncDir(fs.path); err != nil {
fs.log(fmt.Sprintf("syncDir: %v", err))
return err
}
return nil
}
func (fs *fileStorage) SetMeta(fd FileDesc) error {
if !FileDescOk(fd) { if !FileDescOk(fd) {
return ErrInvalidFile return ErrInvalidFile
} }
@ -223,28 +289,10 @@ func (fs *fileStorage) SetMeta(fd FileDesc) (err error) {
if fs.open < 0 { if fs.open < 0 {
return ErrClosed return ErrClosed
} }
defer func() { return fs.setMeta(fd)
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
}
_, 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
}
return rename(path, filepath.Join(fs.path, "CURRENT"))
} }
func (fs *fileStorage) GetMeta() (fd FileDesc, err error) { func (fs *fileStorage) GetMeta() (FileDesc, error) {
fs.mu.Lock() fs.mu.Lock()
defer fs.mu.Unlock() defer fs.mu.Unlock()
if fs.open < 0 { if fs.open < 0 {
@ -252,7 +300,7 @@ func (fs *fileStorage) GetMeta() (fd FileDesc, err error) {
} }
dir, err := os.Open(fs.path) dir, err := os.Open(fs.path)
if err != nil { if err != nil {
return return FileDesc{}, err
} }
names, err := dir.Readdirnames(0) names, err := dir.Readdirnames(0)
// Close the dir first before checking for Readdirnames error. // Close the dir first before checking for Readdirnames error.
@ -260,94 +308,134 @@ func (fs *fileStorage) GetMeta() (fd FileDesc, err error) {
fs.log(fmt.Sprintf("close dir: %v", ce)) fs.log(fmt.Sprintf("close dir: %v", ce))
} }
if err != nil { if err != nil {
return return FileDesc{}, err
} }
// Find latest CURRENT file. // Try this in order:
var rem []string // - CURRENT.[0-9]+ ('pending rename' file, descending order)
var pend bool // - CURRENT
var cerr error // - CURRENT.bak
for _, name := range names { //
if strings.HasPrefix(name, "CURRENT") { // Skip corrupted file or file that point to a missing target file.
pend1 := len(name) > 7 type currentFile struct {
var pendNum int64 name string
// Make sure it is valid name for a CURRENT file, otherwise skip it. fd FileDesc
if pend1 {
if name[7] != '.' || len(name) < 9 {
fs.log(fmt.Sprintf("skipping %s: invalid file name", name))
continue
} }
var e1 error tryCurrent := func(name string) (*currentFile, error) {
if pendNum, e1 = strconv.ParseInt(name[8:], 10, 0); e1 != nil { b, err := ioutil.ReadFile(filepath.Join(fs.path, name))
fs.log(fmt.Sprintf("skipping %s: invalid file num: %v", name, e1)) if err != nil {
continue if os.IsNotExist(err) {
}
}
path := filepath.Join(fs.path, name)
r, e1 := os.OpenFile(path, os.O_RDONLY, 0)
if e1 != nil {
return FileDesc{}, e1
}
b, e1 := ioutil.ReadAll(r)
if e1 != nil {
r.Close()
return FileDesc{}, e1
}
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, name)
}
if !pend1 || cerr == nil {
metaFd, _ := fsParseName(name)
cerr = &ErrCorrupted{
Fd: metaFd,
Err: errors.New("leveldb/storage: corrupted or incomplete meta file"),
}
}
} 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, name)
}
} else {
fd = fd1
pend = pend1
}
if err := r.Close(); err != nil {
fs.log(fmt.Sprintf("close %s: %v", name, err))
}
}
}
// Don't remove any files if there is no valid CURRENT file.
if fd.Zero() {
if cerr != nil {
err = cerr
} else {
err = os.ErrNotExist err = os.ErrNotExist
} }
return return nil, err
} }
if !fs.readOnly { var fd FileDesc
// Rename pending CURRENT file to an effective CURRENT. if len(b) < 1 || b[len(b)-1] != '\n' || !fsParseNamePtr(string(b[:len(b)-1]), &fd) {
if pend { fs.log(fmt.Sprintf("%s: corrupted content: %q", name, b))
path := fmt.Sprintf("%s.%d", filepath.Join(fs.path, "CURRENT"), fd.Num) err := &ErrCorrupted{
if err := rename(path, filepath.Join(fs.path, "CURRENT")); err != nil { Err: errors.New("leveldb/storage: corrupted or incomplete CURRENT file"),
fs.log(fmt.Sprintf("CURRENT.%d -> CURRENT: %v", fd.Num, err)) }
return nil, err
}
if _, err := os.Stat(filepath.Join(fs.path, fsGenName(fd))); err != nil {
if os.IsNotExist(err) {
fs.log(fmt.Sprintf("%s: missing target file: %s", name, fd))
err = os.ErrNotExist
}
return nil, err
}
return &currentFile{name: name, fd: fd}, nil
}
tryCurrents := func(names []string) (*currentFile, error) {
var (
cur *currentFile
// Last corruption error.
lastCerr error
)
for _, name := range names {
var err error
cur, err = tryCurrent(name)
if err == nil {
break
} else if err == os.ErrNotExist {
// Fallback to the next file.
} else if isCorrupted(err) {
lastCerr = err
// Fallback to the next file.
} else {
// In case the error is due to permission, etc.
return nil, err
} }
} }
// Remove obsolete or incomplete pending CURRENT files. if cur == nil {
for _, name := range rem { err := os.ErrNotExist
path := filepath.Join(fs.path, name) if lastCerr != nil {
if err := os.Remove(path); err != nil { err = lastCerr
}
return nil, err
}
return cur, nil
}
// Try 'pending rename' files.
var nums []int64
for _, name := range names {
if strings.HasPrefix(name, "CURRENT.") && name != "CURRENT.bak" {
i, err := strconv.ParseInt(name[8:], 10, 64)
if err == nil {
nums = append(nums, i)
}
}
}
var (
pendCur *currentFile
pendErr = os.ErrNotExist
pendNames []string
)
if len(nums) > 0 {
sort.Sort(sort.Reverse(int64Slice(nums)))
pendNames = make([]string, len(nums))
for i, num := range nums {
pendNames[i] = fmt.Sprintf("CURRENT.%d", num)
}
pendCur, pendErr = tryCurrents(pendNames)
if pendErr != nil && pendErr != os.ErrNotExist && !isCorrupted(pendErr) {
return FileDesc{}, pendErr
}
}
// Try CURRENT and CURRENT.bak.
curCur, curErr := tryCurrents([]string{"CURRENT", "CURRENT.bak"})
if curErr != nil && curErr != os.ErrNotExist && !isCorrupted(curErr) {
return FileDesc{}, curErr
}
// pendCur takes precedence, but guards against obsolete pendCur.
if pendCur != nil && (curCur == nil || pendCur.fd.Num > curCur.fd.Num) {
curCur = pendCur
}
if curCur != nil {
// Restore CURRENT file to proper state.
if !fs.readOnly && (curCur.name != "CURRENT" || len(pendNames) != 0) {
// Ignore setMeta errors, however don't delete obsolete files if we
// catch error.
if err := fs.setMeta(curCur.fd); err == nil {
// Remove 'pending rename' files.
for _, name := range pendNames {
if err := os.Remove(filepath.Join(fs.path, name)); err != nil {
fs.log(fmt.Sprintf("remove %s: %v", name, err)) fs.log(fmt.Sprintf("remove %s: %v", name, err))
} }
} }
} }
return }
return curCur.fd, nil
}
// Nothing found.
if isCorrupted(pendErr) {
return FileDesc{}, pendErr
}
return FileDesc{}, curErr
} }
func (fs *fileStorage) List(ft FileType) (fds []FileDesc, err error) { func (fs *fileStorage) List(ft FileType) (fds []FileDesc, err error) {

View file

@ -8,7 +8,6 @@ package storage
import ( import (
"os" "os"
"path/filepath"
) )
type plan9FileLock struct { type plan9FileLock struct {
@ -48,8 +47,7 @@ func rename(oldpath, newpath string) error {
} }
} }
_, fname := filepath.Split(newpath) return os.Rename(oldpath, newpath)
return os.Rename(oldpath, fname)
} }
func syncDir(name string) error { func syncDir(name string) error {

View file

@ -67,13 +67,25 @@ func isErrInvalid(err error) bool {
if err == os.ErrInvalid { if err == os.ErrInvalid {
return true return true
} }
// Go < 1.8
if syserr, ok := err.(*os.SyscallError); ok && syserr.Err == syscall.EINVAL { if syserr, ok := err.(*os.SyscallError); ok && syserr.Err == syscall.EINVAL {
return true return true
} }
// Go >= 1.8 returns *os.PathError instead
if patherr, ok := err.(*os.PathError); ok && patherr.Err == syscall.EINVAL {
return true
}
return false return false
} }
func syncDir(name string) error { func syncDir(name string) error {
// As per fsync manpage, Linux seems to expect fsync on directory, however
// some system don't support this, so we will ignore syscall.EINVAL.
//
// From fsync(2):
// Calling fsync() does not necessarily ensure that the entry in the
// directory containing the file has also reached disk. For that an
// explicit fsync() on a file descriptor for the directory is also needed.
f, err := os.Open(name) f, err := os.Open(name)
if err != nil { if err != nil {
return err return err

View file

@ -12,7 +12,11 @@ import (
"sync" "sync"
) )
const typeShift = 3 const typeShift = 4
// Verify at compile-time that typeShift is large enough to cover all FileType
// values by confirming that 0 == 0.
var _ [0]struct{} = [TypeAll >> typeShift]struct{}{}
type memStorageLock struct { type memStorageLock struct {
ms *memStorage ms *memStorage
@ -143,7 +147,7 @@ func (ms *memStorage) Remove(fd FileDesc) error {
} }
func (ms *memStorage) Rename(oldfd, newfd FileDesc) error { func (ms *memStorage) Rename(oldfd, newfd FileDesc) error {
if FileDescOk(oldfd) || FileDescOk(newfd) { if !FileDescOk(oldfd) || !FileDescOk(newfd) {
return ErrInvalidFile return ErrInvalidFile
} }
if oldfd == newfd { if oldfd == newfd {

View file

@ -55,6 +55,14 @@ type ErrCorrupted struct {
Err error Err error
} }
func isCorrupted(err error) bool {
switch err.(type) {
case *ErrCorrupted:
return true
}
return false
}
func (e *ErrCorrupted) Error() string { func (e *ErrCorrupted) Error() string {
if !e.Fd.Zero() { if !e.Fd.Zero() {
return fmt.Sprintf("%v [file=%v]", e.Err, e.Fd) return fmt.Sprintf("%v [file=%v]", e.Err, e.Fd)

View file

@ -78,7 +78,7 @@ func newTableFile(fd storage.FileDesc, size int64, imin, imax internalKey) *tFil
} }
func tableFileFromRecord(r atRecord) *tFile { func tableFileFromRecord(r atRecord) *tFile {
return newTableFile(storage.FileDesc{storage.TypeTable, r.num}, r.size, r.imin, r.imax) return newTableFile(storage.FileDesc{Type: storage.TypeTable, Num: r.num}, r.size, r.imin, r.imax)
} }
// tFiles hold multiple tFile. // tFiles hold multiple tFile.
@ -292,6 +292,7 @@ func (x *tFilesSortByNum) Less(i, j int) bool {
type tOps struct { type tOps struct {
s *session s *session
noSync bool noSync bool
evictRemoved bool
cache *cache.Cache cache *cache.Cache
bcache *cache.Cache bcache *cache.Cache
bpool *util.BufferPool bpool *util.BufferPool
@ -299,7 +300,7 @@ type tOps struct {
// Creates an empty table and returns table writer. // Creates an empty table and returns table writer.
func (t *tOps) create() (*tWriter, error) { func (t *tOps) create() (*tWriter, error) {
fd := storage.FileDesc{storage.TypeTable, t.s.allocFileNum()} fd := storage.FileDesc{Type: storage.TypeTable, Num: t.s.allocFileNum()}
fw, err := t.s.stor.Create(fd) fw, err := t.s.stor.Create(fd)
if err != nil { if err != nil {
return nil, err return nil, err
@ -422,7 +423,7 @@ func (t *tOps) remove(f *tFile) {
} else { } else {
t.s.logf("table@remove removed @%d", f.fd.Num) t.s.logf("table@remove removed @%d", f.fd.Num)
} }
if t.bcache != nil { if t.evictRemoved && t.bcache != nil {
t.bcache.EvictNS(uint64(f.fd.Num)) t.bcache.EvictNS(uint64(f.fd.Num))
} }
}) })
@ -451,7 +452,7 @@ func newTableOps(s *session) *tOps {
if !s.o.GetDisableBlockCache() { if !s.o.GetDisableBlockCache() {
var bcacher cache.Cacher var bcacher cache.Cacher
if s.o.GetBlockCacheCapacity() > 0 { if s.o.GetBlockCacheCapacity() > 0 {
bcacher = cache.NewLRU(s.o.GetBlockCacheCapacity()) bcacher = s.o.GetBlockCacher().New(s.o.GetBlockCacheCapacity())
} }
bcache = cache.NewCache(bcacher) bcache = cache.NewCache(bcacher)
} }
@ -461,6 +462,7 @@ func newTableOps(s *session) *tOps {
return &tOps{ return &tOps{
s: s, s: s,
noSync: s.o.GetNoSync(), noSync: s.o.GetNoSync(),
evictRemoved: s.o.GetBlockCacheEvictRemoved(),
cache: cache.NewCache(cacher), cache: cache.NewCache(cacher),
bcache: bcache, bcache: bcache,
bpool: bpool, bpool: bpool,

View file

@ -581,6 +581,7 @@ func (r *Reader) readRawBlock(bh blockHandle, verifyChecksum bool) ([]byte, erro
case blockTypeSnappyCompression: case blockTypeSnappyCompression:
decLen, err := snappy.DecodedLen(data[:bh.length]) decLen, err := snappy.DecodedLen(data[:bh.length])
if err != nil { if err != nil {
r.bpool.Put(data)
return nil, r.newErrCorruptedBH(bh, err.Error()) return nil, r.newErrCorruptedBH(bh, err.Error())
} }
decData := r.bpool.Get(decLen) decData := r.bpool.Get(decLen)

View file

@ -20,7 +20,7 @@ func shorten(str string) string {
return str[:3] + ".." + str[len(str)-3:] return str[:3] + ".." + str[len(str)-3:]
} }
var bunits = [...]string{"", "Ki", "Mi", "Gi"} var bunits = [...]string{"", "Ki", "Mi", "Gi", "Ti"}
func shortenb(bytes int) string { func shortenb(bytes int) string {
i := 0 i := 0

View file

@ -19,7 +19,7 @@ var (
// Releaser is the interface that wraps the basic Release method. // Releaser is the interface that wraps the basic Release method.
type Releaser interface { type Releaser interface {
// Release releases associated resources. Release should always success // Release releases associated resources. Release should always success
// and can be called multipe times without causing error. // and can be called multiple times without causing error.
Release() Release()
} }