1
0
Эх сурвалжийг харах

Update goleveldb (fixes #644, closes #648)

Jakob Borg 11 жил өмнө
parent
commit
64ffac5671

+ 1 - 1
Godeps/Godeps.json

@@ -49,7 +49,7 @@
 		},
 		{
 			"ImportPath": "github.com/syndtr/goleveldb/leveldb",
-			"Rev": "2b99e8d4757bf06eeab1b0485d80b8ae1c088874"
+			"Rev": "457e6f75905f7c1316afd8c43ad323f4c32b31c2"
 		},
 		{
 			"ImportPath": "github.com/vitrun/qart/coding",

+ 2 - 1
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/cache.go

@@ -128,7 +128,8 @@ const (
 type nodeState int
 
 const (
-	nodeEffective nodeState = iota
+	nodeZero nodeState = iota
+	nodeEffective
 	nodeEvicted
 	nodeDeleted
 )

+ 53 - 3
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/cache_test.go

@@ -512,6 +512,56 @@ func TestLRUCache_Finalizer(t *testing.T) {
 	}
 }
 
+func BenchmarkLRUCache_Set(b *testing.B) {
+	c := NewLRUCache(0)
+	ns := c.GetNamespace(0)
+	b.ResetTimer()
+	for i := uint64(0); i < uint64(b.N); i++ {
+		set(ns, i, "", 1, nil)
+	}
+}
+
+func BenchmarkLRUCache_Get(b *testing.B) {
+	c := NewLRUCache(0)
+	ns := c.GetNamespace(0)
+	b.ResetTimer()
+	for i := uint64(0); i < uint64(b.N); i++ {
+		set(ns, i, "", 1, nil)
+	}
+	b.ResetTimer()
+	for i := uint64(0); i < uint64(b.N); i++ {
+		ns.Get(i, nil)
+	}
+}
+
+func BenchmarkLRUCache_Get2(b *testing.B) {
+	c := NewLRUCache(0)
+	ns := c.GetNamespace(0)
+	b.ResetTimer()
+	for i := uint64(0); i < uint64(b.N); i++ {
+		set(ns, i, "", 1, nil)
+	}
+	b.ResetTimer()
+	for i := uint64(0); i < uint64(b.N); i++ {
+		ns.Get(i, func() (charge int, value interface{}) {
+			return 0, nil
+		})
+	}
+}
+
+func BenchmarkLRUCache_Release(b *testing.B) {
+	c := NewLRUCache(0)
+	ns := c.GetNamespace(0)
+	handles := make([]Handle, b.N)
+	for i := uint64(0); i < uint64(b.N); i++ {
+		handles[i] = set(ns, i, "", 1, nil)
+	}
+	b.ResetTimer()
+	for _, h := range handles {
+		h.Release()
+	}
+}
+
 func BenchmarkLRUCache_SetRelease(b *testing.B) {
 	capacity := b.N / 100
 	if capacity <= 0 {
@@ -521,7 +571,7 @@ func BenchmarkLRUCache_SetRelease(b *testing.B) {
 	ns := c.GetNamespace(0)
 	b.ResetTimer()
 	for i := uint64(0); i < uint64(b.N); i++ {
-		set(ns, i, nil, 1, nil).Release()
+		set(ns, i, "", 1, nil).Release()
 	}
 }
 
@@ -538,10 +588,10 @@ func BenchmarkLRUCache_SetReleaseTwice(b *testing.B) {
 	nb := b.N - na
 
 	for i := uint64(0); i < uint64(na); i++ {
-		set(ns, i, nil, 1, nil).Release()
+		set(ns, i, "", 1, nil).Release()
 	}
 
 	for i := uint64(0); i < uint64(nb); i++ {
-		set(ns, i, nil, 1, nil).Release()
+		set(ns, i, "", 1, nil).Release()
 	}
 }

+ 324 - 93
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/cache/lru_cache.go

@@ -13,6 +13,13 @@ import (
 	"github.com/syndtr/goleveldb/leveldb/util"
 )
 
+// The LLRB implementation were taken from https://github.com/petar/GoLLRB,
+// which conatins the following header:
+//
+// Copyright 2010 Petar Maymounkov. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
 // lruCache represent a LRU cache state.
 type lruCache struct {
 	mu                sync.Mutex
@@ -74,11 +81,7 @@ func (c *lruCache) GetNamespace(id uint64) Namespace {
 		return ns
 	}
 
-	ns := &lruNs{
-		lru:   c,
-		id:    id,
-		table: make(map[uint64]*lruNode),
-	}
+	ns := &lruNs{lru: c, id: id}
 	c.table[id] = ns
 	return ns
 }
@@ -130,130 +133,267 @@ func (c *lruCache) evict() {
 }
 
 type lruNs struct {
-	lru   *lruCache
-	id    uint64
-	table map[uint64]*lruNode
-	state nsState
+	lru    *lruCache
+	id     uint64
+	rbRoot *lruNode
+	state  nsState
 }
 
-func (ns *lruNs) Get(key uint64, setf SetFunc) Handle {
-	ns.lru.mu.Lock()
+func (ns *lruNs) rbGetOrCreateNode(h *lruNode, key uint64) (hn, n *lruNode) {
+	if h == nil {
+		n = &lruNode{ns: ns, key: key}
+		return n, n
+	}
 
-	if ns.state != nsEffective {
-		ns.lru.mu.Unlock()
+	if key < h.key {
+		hn, n = ns.rbGetOrCreateNode(h.rbLeft, key)
+		if hn != nil {
+			h.rbLeft = hn
+		} else {
+			return nil, n
+		}
+	} else if key > h.key {
+		hn, n = ns.rbGetOrCreateNode(h.rbRight, key)
+		if hn != nil {
+			h.rbRight = hn
+		} else {
+			return nil, n
+		}
+	} else {
+		return nil, h
+	}
+
+	if rbIsRed(h.rbRight) && !rbIsRed(h.rbLeft) {
+		h = rbRotLeft(h)
+	}
+	if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) {
+		h = rbRotRight(h)
+	}
+	if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) {
+		rbFlip(h)
+	}
+	return h, n
+}
+
+func (ns *lruNs) getOrCreateNode(key uint64) *lruNode {
+	hn, n := ns.rbGetOrCreateNode(ns.rbRoot, key)
+	if hn != nil {
+		ns.rbRoot = hn
+		ns.rbRoot.rbBlack = true
+	}
+	return n
+}
+
+func (ns *lruNs) rbGetNode(key uint64) *lruNode {
+	h := ns.rbRoot
+	for h != nil {
+		switch {
+		case key < h.key:
+			h = h.rbLeft
+		case key > h.key:
+			h = h.rbRight
+		default:
+			return h
+		}
+	}
+	return nil
+}
+
+func (ns *lruNs) getNode(key uint64) *lruNode {
+	return ns.rbGetNode(key)
+}
+
+func (ns *lruNs) rbDeleteNode(h *lruNode, key uint64) *lruNode {
+	if h == nil {
 		return nil
 	}
 
-	node, ok := ns.table[key]
-	if ok {
-		switch node.state {
-		case nodeEvicted:
-			// Insert to recent list.
-			node.state = nodeEffective
-			node.ref++
-			ns.lru.used += node.charge
-			ns.lru.evict()
-			fallthrough
-		case nodeEffective:
-			// Bump to front.
-			node.rRemove()
-			node.rInsert(&ns.lru.recent)
+	if key < h.key {
+		if h.rbLeft == nil { // key not present. Nothing to delete
+			return h
+		}
+		if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) {
+			h = rbMoveLeft(h)
 		}
-		node.ref++
+		h.rbLeft = ns.rbDeleteNode(h.rbLeft, key)
 	} else {
-		if setf == nil {
-			ns.lru.mu.Unlock()
+		if rbIsRed(h.rbLeft) {
+			h = rbRotRight(h)
+		}
+		// If @key equals @h.key and no right children at @h
+		if h.key == key && h.rbRight == nil {
 			return nil
 		}
+		if h.rbRight != nil && !rbIsRed(h.rbRight) && !rbIsRed(h.rbRight.rbLeft) {
+			h = rbMoveRight(h)
+		}
+		// If @key equals @h.key, and (from above) 'h.Right != nil'
+		if h.key == key {
+			var x *lruNode
+			h.rbRight, x = rbDeleteMin(h.rbRight)
+			if x == nil {
+				panic("logic")
+			}
+			x.rbLeft, h.rbLeft = h.rbLeft, nil
+			x.rbRight, h.rbRight = h.rbRight, nil
+			x.rbBlack = h.rbBlack
+			h = x
+		} else { // Else, @key is bigger than @h.key
+			h.rbRight = ns.rbDeleteNode(h.rbRight, key)
+		}
+	}
+
+	return rbFixup(h)
+}
+
+func (ns *lruNs) deleteNode(key uint64) {
+	ns.rbRoot = ns.rbDeleteNode(ns.rbRoot, key)
+	if ns.rbRoot != nil {
+		ns.rbRoot.rbBlack = true
+	}
+}
 
+func (ns *lruNs) rbIterateNodes(h *lruNode, pivot uint64, iter func(n *lruNode) bool) bool {
+	if h == nil {
+		return true
+	}
+	if h.key >= pivot {
+		if !ns.rbIterateNodes(h.rbLeft, pivot, iter) {
+			return false
+		}
+		if !iter(h) {
+			return false
+		}
+	}
+	return ns.rbIterateNodes(h.rbRight, pivot, iter)
+}
+
+func (ns *lruNs) iterateNodes(iter func(n *lruNode) bool) {
+	ns.rbIterateNodes(ns.rbRoot, 0, iter)
+}
+
+func (ns *lruNs) Get(key uint64, setf SetFunc) Handle {
+	ns.lru.mu.Lock()
+	defer ns.lru.mu.Unlock()
+
+	if ns.state != nsEffective {
+		return nil
+	}
+
+	var n *lruNode
+	if setf == nil {
+		n = ns.getNode(key)
+		if n == nil {
+			return nil
+		}
+	} else {
+		n = ns.getOrCreateNode(key)
+	}
+	switch n.state {
+	case nodeZero:
 		charge, value := setf()
 		if value == nil {
-			ns.lru.mu.Unlock()
+			ns.deleteNode(key)
 			return nil
 		}
-
-		node = &lruNode{
-			ns:     ns,
-			key:    key,
-			value:  value,
-			charge: charge,
-			ref:    1,
+		if charge < 0 {
+			charge = 0
 		}
-		ns.table[key] = node
+
+		n.value = value
+		n.charge = charge
+		n.state = nodeEvicted
 
 		ns.lru.size += charge
 		ns.lru.alive++
-		if charge > 0 {
-			node.ref++
-			node.rInsert(&ns.lru.recent)
-			ns.lru.used += charge
-			ns.lru.evict()
+
+		fallthrough
+	case nodeEvicted:
+		if n.charge == 0 {
+			break
 		}
+
+		// Insert to recent list.
+		n.state = nodeEffective
+		n.ref++
+		ns.lru.used += n.charge
+		ns.lru.evict()
+
+		fallthrough
+	case nodeEffective:
+		// Bump to front.
+		n.rRemove()
+		n.rInsert(&ns.lru.recent)
 	}
+	n.ref++
 
-	ns.lru.mu.Unlock()
-	return &lruHandle{node: node}
+	return &lruHandle{node: n}
 }
 
 func (ns *lruNs) Delete(key uint64, fin DelFin) bool {
 	ns.lru.mu.Lock()
+	defer ns.lru.mu.Unlock()
 
 	if ns.state != nsEffective {
 		if fin != nil {
 			fin(false, false)
 		}
-		ns.lru.mu.Unlock()
 		return false
 	}
 
-	node, exist := ns.table[key]
-	if !exist {
+	n := ns.getNode(key)
+	if n == nil {
 		if fin != nil {
 			fin(false, false)
 		}
-		ns.lru.mu.Unlock()
 		return false
+
 	}
 
-	switch node.state {
+	switch n.state {
+	case nodeEffective:
+		ns.lru.used -= n.charge
+		n.state = nodeDeleted
+		n.delfin = fin
+		n.rRemove()
+		n.derefNB()
+	case nodeEvicted:
+		n.state = nodeDeleted
+		n.delfin = fin
 	case nodeDeleted:
 		if fin != nil {
 			fin(true, true)
 		}
-		ns.lru.mu.Unlock()
 		return false
-	case nodeEffective:
-		ns.lru.used -= node.charge
-		node.state = nodeDeleted
-		node.delfin = fin
-		node.rRemove()
-		node.derefNB()
 	default:
-		node.state = nodeDeleted
-		node.delfin = fin
+		panic("invalid state")
 	}
 
-	ns.lru.mu.Unlock()
 	return true
 }
 
 func (ns *lruNs) purgeNB(fin PurgeFin) {
-	if ns.state != nsEffective {
-		return
-	}
-
-	for _, node := range ns.table {
-		switch node.state {
-		case nodeDeleted:
-		case nodeEffective:
-			ns.lru.used -= node.charge
-			node.state = nodeDeleted
-			node.purgefin = fin
-			node.rRemove()
-			node.derefNB()
-		default:
-			node.state = nodeDeleted
-			node.purgefin = fin
+	if ns.state == nsEffective {
+		var nodes []*lruNode
+		ns.iterateNodes(func(n *lruNode) bool {
+			nodes = append(nodes, n)
+			return true
+		})
+		for _, n := range nodes {
+			switch n.state {
+			case nodeEffective:
+				ns.lru.used -= n.charge
+				n.state = nodeDeleted
+				n.purgefin = fin
+				n.rRemove()
+				n.derefNB()
+			case nodeEvicted:
+				n.state = nodeDeleted
+				n.purgefin = fin
+			case nodeDeleted:
+			default:
+				panic("invalid state")
+			}
 		}
 	}
 }
@@ -265,22 +405,22 @@ func (ns *lruNs) Purge(fin PurgeFin) {
 }
 
 func (ns *lruNs) zapNB() {
-	if ns.state != nsEffective {
-		return
-	}
-
-	ns.state = nsZapped
+	if ns.state == nsEffective {
+		ns.state = nsZapped
+
+		ns.iterateNodes(func(n *lruNode) bool {
+			if n.state == nodeEffective {
+				ns.lru.used -= n.charge
+				n.rRemove()
+			}
+			ns.lru.size -= n.charge
+			n.state = nodeDeleted
+			n.fin()
 
-	for _, node := range ns.table {
-		if node.state == nodeEffective {
-			ns.lru.used -= node.charge
-			node.rRemove()
-		}
-		ns.lru.size -= node.charge
-		node.state = nodeDeleted
-		node.fin()
+			return true
+		})
+		ns.rbRoot = nil
 	}
-	ns.table = nil
 }
 
 func (ns *lruNs) Zap() {
@@ -293,7 +433,9 @@ func (ns *lruNs) Zap() {
 type lruNode struct {
 	ns *lruNs
 
-	rNext, rPrev *lruNode
+	rNext, rPrev    *lruNode
+	rbLeft, rbRight *lruNode
+	rbBlack         bool
 
 	key      uint64
 	value    interface{}
@@ -344,7 +486,7 @@ func (n *lruNode) derefNB() {
 	if n.ref == 0 {
 		if n.ns.state == nsEffective {
 			// Remove elemement.
-			delete(n.ns.table, n.key)
+			n.ns.deleteNode(n.key)
 			n.ns.lru.size -= n.charge
 			n.ns.lru.alive--
 			n.fin()
@@ -380,3 +522,92 @@ func (h *lruHandle) Release() {
 	h.node.deref()
 	h.node = nil
 }
+
+func rbIsRed(h *lruNode) bool {
+	if h == nil {
+		return false
+	}
+	return !h.rbBlack
+}
+
+func rbRotLeft(h *lruNode) *lruNode {
+	x := h.rbRight
+	if x.rbBlack {
+		panic("rotating a black link")
+	}
+	h.rbRight = x.rbLeft
+	x.rbLeft = h
+	x.rbBlack = h.rbBlack
+	h.rbBlack = false
+	return x
+}
+
+func rbRotRight(h *lruNode) *lruNode {
+	x := h.rbLeft
+	if x.rbBlack {
+		panic("rotating a black link")
+	}
+	h.rbLeft = x.rbRight
+	x.rbRight = h
+	x.rbBlack = h.rbBlack
+	h.rbBlack = false
+	return x
+}
+
+func rbFlip(h *lruNode) {
+	h.rbBlack = !h.rbBlack
+	h.rbLeft.rbBlack = !h.rbLeft.rbBlack
+	h.rbRight.rbBlack = !h.rbRight.rbBlack
+}
+
+func rbMoveLeft(h *lruNode) *lruNode {
+	rbFlip(h)
+	if rbIsRed(h.rbRight.rbLeft) {
+		h.rbRight = rbRotRight(h.rbRight)
+		h = rbRotLeft(h)
+		rbFlip(h)
+	}
+	return h
+}
+
+func rbMoveRight(h *lruNode) *lruNode {
+	rbFlip(h)
+	if rbIsRed(h.rbLeft.rbLeft) {
+		h = rbRotRight(h)
+		rbFlip(h)
+	}
+	return h
+}
+
+func rbFixup(h *lruNode) *lruNode {
+	if rbIsRed(h.rbRight) {
+		h = rbRotLeft(h)
+	}
+
+	if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) {
+		h = rbRotRight(h)
+	}
+
+	if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) {
+		rbFlip(h)
+	}
+
+	return h
+}
+
+func rbDeleteMin(h *lruNode) (hn, n *lruNode) {
+	if h == nil {
+		return nil, nil
+	}
+	if h.rbLeft == nil {
+		return nil, h
+	}
+
+	if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) {
+		h = rbMoveLeft(h)
+	}
+
+	h.rbLeft, n = rbDeleteMin(h.rbLeft)
+
+	return rbFixup(h), n
+}

+ 1 - 1
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/db_test.go

@@ -1210,7 +1210,7 @@ func TestDb_DeletionMarkers2(t *testing.T) {
 }
 
 func TestDb_CompactionTableOpenError(t *testing.T) {
-	h := newDbHarnessWopt(t, &opt.Options{MaxOpenFiles: 0})
+	h := newDbHarnessWopt(t, &opt.Options{CachedOpenFiles: -1})
 	defer h.close()
 
 	im := 10

+ 1 - 1
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/external_test.go

@@ -21,7 +21,7 @@ var _ = testutil.Defer(func() {
 			BlockRestartInterval: 5,
 			BlockSize:            50,
 			Compression:          opt.NoCompression,
-			MaxOpenFiles:         0,
+			CachedOpenFiles:      -1,
 			Strict:               opt.StrictAll,
 			WriteBuffer:          1000,
 		}

+ 17 - 15
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt/options.go

@@ -24,7 +24,7 @@ const (
 	DefaultBlockRestartInterval = 16
 	DefaultBlockSize            = 4 * KiB
 	DefaultCompressionType      = SnappyCompression
-	DefaultMaxOpenFiles         = 1000
+	DefaultCachedOpenFiles      = 500
 	DefaultWriteBuffer          = 4 * MiB
 )
 
@@ -125,6 +125,13 @@ type Options struct {
 	// The default value is 4KiB.
 	BlockSize int
 
+	// CachedOpenFiles defines number of open files to kept around when not
+	// in-use, the counting includes still in-use files.
+	// Set this to negative value to disable caching.
+	//
+	// The default value is 500.
+	CachedOpenFiles int
+
 	// Comparer defines a total ordering over the space of []byte keys: a 'less
 	// than' relationship. The same comparison algorithm must be used for reads
 	// and writes over the lifetime of the DB.
@@ -165,13 +172,6 @@ type Options struct {
 	// The default value is nil.
 	Filter filter.Filter
 
-	// MaxOpenFiles defines maximum number of open files to kept around
-	// (cached). This is not an hard limit, actual open files may exceed
-	// the defined value.
-	//
-	// The default value is 1000.
-	MaxOpenFiles int
-
 	// Strict defines the DB strict level.
 	Strict Strict
 
@@ -213,6 +213,15 @@ func (o *Options) GetBlockSize() int {
 	return o.BlockSize
 }
 
+func (o *Options) GetCachedOpenFiles() int {
+	if o == nil || o.CachedOpenFiles == 0 {
+		return DefaultCachedOpenFiles
+	} else if o.CachedOpenFiles < 0 {
+		return 0
+	}
+	return o.CachedOpenFiles
+}
+
 func (o *Options) GetComparer() comparer.Comparer {
 	if o == nil || o.Comparer == nil {
 		return comparer.DefaultComparer
@@ -248,13 +257,6 @@ func (o *Options) GetFilter() filter.Filter {
 	return o.Filter
 }
 
-func (o *Options) GetMaxOpenFiles() int {
-	if o == nil || o.MaxOpenFiles <= 0 {
-		return DefaultMaxOpenFiles
-	}
-	return o.MaxOpenFiles
-}
-
 func (o *Options) GetStrict(strict Strict) bool {
 	if o == nil || o.Strict == 0 {
 		return DefaultStrict&strict != 0

+ 1 - 1
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/session.go

@@ -58,7 +58,7 @@ func newSession(stor storage.Storage, o *opt.Options) (s *session, err error) {
 		storLock: storLock,
 	}
 	s.setOptions(o)
-	s.tops = newTableOps(s, s.o.GetMaxOpenFiles())
+	s.tops = newTableOps(s, s.o.GetCachedOpenFiles())
 	s.setVersion(&version{s: s})
 	s.log("log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock D·DeletedEntry L·Level Q·SeqNum T·TimeElapsed")
 	return

+ 1 - 0
Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/util/range.go

@@ -25,6 +25,7 @@ func BytesPrefix(prefix []byte) *Range {
 			limit = make([]byte, i+1)
 			copy(limit, prefix)
 			limit[i] = c + 1
+			break
 		}
 	}
 	return &Range{prefix, limit}

+ 1 - 1
cmd/syncthing/main.go

@@ -391,7 +391,7 @@ func syncthingMain() {
 	// If this is the first time the user runs v0.9, archive the old indexes and config.
 	archiveLegacyConfig()
 
-	db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{MaxOpenFiles: 100})
+	db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{CachedOpenFiles: 100})
 	if err != nil {
 		l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
 	}