-
Notifications
You must be signed in to change notification settings - Fork 23
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Various exported types could use some comment docs.
We can probably avoid append in many cases for efficiency.
Otherwise cool
db/cache_db.go
Outdated
// Not the best, but probably not a bottleneck depending. | ||
keys := []string{} | ||
for key, _ := range db.cache { | ||
keys = append(keys, key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at the least we can do this without append by allocating up front
db/cache_db.go
Outdated
db.mtx.Lock() | ||
defer db.mtx.Unlock() | ||
|
||
db.cache[string(key)] = cDBValue{nil, true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not DeleteNoLock?
db/cache_db.go
Outdated
db.mtx.Lock() | ||
defer db.mtx.Unlock() | ||
|
||
db.cache[string(key)] = cDBValue{nil, true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
db/cache_db.go
Outdated
|
||
fmt.Println("CacheDB\ncache:\n") | ||
for key, value := range db.cache { | ||
fmt.Printf("[%X]:\t[%X]\n", []byte(key), value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need the []byte(key)
? I think key
suffices
db/mem_batch.go
Outdated
defer mtx.Unlock() | ||
|
||
for _, op := range mBatch.ops { | ||
if op.opType == opTypeSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe nicer to use a switch
if strings.HasPrefix(key, string(prefix)) { | ||
it.keys = append(it.keys, key) | ||
} | ||
it.keys = append(it.keys, key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can allocate up front
merkle/kvpairs.go
Outdated
|
||
func MakeSortedKVPairs(m map[string]interface{}) []Hashable { | ||
kvPairs := []KVPair{} | ||
for k, v := range m { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allocate up front
merkle/simple_map.go
Outdated
func (sm *SimpleMap) Hash() []byte { | ||
sm.kvz.Sort() | ||
kvPairsH := []Hashable{} | ||
for _, kvp := range sm.kvz { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allocate up front
merkle/simple_map_test.go
Outdated
} | ||
{ | ||
db := NewSimpleMap() | ||
db.Set("key1", "value1") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duplicate test case ?
|
db/backend_test.go
Outdated
common "github.com/tendermint/go-common" | ||
) | ||
|
||
func print(x []byte) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't see where it's being used...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't this repo die long ago? To be reborn as tmlibs
db/c_level_db.go
Outdated
} | ||
|
||
func (c cLevelDBIterator) Next() bool { | ||
c.Next() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
infinite recursion? maybe c.(*levigo.Iterator).Next()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c.Iterator.Next() should work...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or perhaps let's make it explicit to
type cLevelDBIterator struct {
iter *levigo.Iterator
}
func (c cLevelDBIterator) Next() bool {
c.iter.Next()
}
to avoid such problems that we previously had with 0b22b27 where the problem slipped through the cracks?
@@ -3,15 +3,14 @@ package db | |||
import . "github.com/tendermint/tmlibs/common" | |||
|
|||
type DB interface { | |||
Get([]byte) []byte | |||
Get([]byte) []byte // NOTE: returns nil iff never set or deleted. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iff
means if and only if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did not know, thanks!
db/go_level_db.go
Outdated
@@ -155,10 +154,6 @@ func (db *GoLevelDB) Iterator() Iterator { | |||
return &goLevelDBIterator{db.db.NewIterator(nil, nil)} | |||
} | |||
|
|||
func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why delete? nobody using it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using this in iavl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should take a look at levigo/goleveldb's iterators and what's supported there... first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LeviGo supports Seek... https://github.com/jmhodges/levigo/blob/master/iterator.go#L121
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So does Goleveldb: https://godoc.org/github.com/syndtr/goleveldb/leveldb/iterator#IteratorSeeker
Lets adopt these as tmlibs/db.Iterator, and make MemDB support it too...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll write an exposed module function called "IteratePrefix" that takes an iterator and calls Seek() accordingly to get the same effect.
merkle/simple_map.go
Outdated
func (sm *SimpleMap) Hash() []byte { | ||
sm.kvz.Sort() | ||
kvPairsH := []Hashable{} | ||
for _, kvp := range sm.kvz { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and what if the value is not Hashable
? I am asking because in KVPair#Hash
there is an if if kvH, ok := kv.Value.(Hashable); ok {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The KVPair is a Hashable that knows how to deal with binary objects.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We make heavy use of IteratorPrefix in iavl and it's all-around useful for iterating over ordered key-spaces, so we should keep it.
Set([]byte, []byte) | ||
SetSync([]byte, []byte) | ||
Delete([]byte) | ||
DeleteSync([]byte) | ||
Close() | ||
NewBatch() Batch | ||
Iterator() Iterator | ||
IteratorPrefix([]byte) Iterator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need this for iavl.
db/go_level_db.go
Outdated
@@ -155,10 +154,6 @@ func (db *GoLevelDB) Iterator() Iterator { | |||
return &goLevelDBIterator{db.db.NewIterator(nil, nil)} | |||
} | |||
|
|||
func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using this in iavl.
merkle/simple_map_test.go
Outdated
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
// "github.com/stretchr/testify/require" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can delete this.
Makefile
Outdated
@@ -33,15 +33,15 @@ metalinter_test: ensure_tools | |||
--enable=goconst \ | |||
--enable=gosimple \ | |||
--enable=ineffassign \ | |||
--enable=interfacer \ | |||
--enable=interfacer \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why did all this spacing get weird?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mh, some had two spaces instead of tabs. Everything is now intended with 2 tabs.
db/c_level_db.go
Outdated
@@ -49,6 +51,8 @@ func NewCLevelDB(name string, dir string) (*CLevelDB, error) { | |||
return database, nil | |||
} | |||
|
|||
// Get gets the value for the given key. It returns ErrNotFound if the DB does not contains the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't sound right. The function panics on err, and should return nil if the key is not there. Is that not what it does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. That doesn't sound right. I think I might have thought about something else while typing this :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These comments are not necessary, we should just comment the interface functions (for DB and Iterator)
db/c_level_db.go
Outdated
@@ -19,13 +19,15 @@ func init() { | |||
registerDBCreator(CLevelDBBackendStr, dbCreator, false) | |||
} | |||
|
|||
// CLevelDB provides access to the C implementation of LevelDB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a XXX comment that these methods panic on error from the underlying DB
db/cache_db.go
Outdated
@@ -37,63 +41,77 @@ func (db *CacheDB) Get(key []byte) []byte { | |||
return db.parent.Get(key) | |||
} | |||
|
|||
// Set sets the value for the given key in the cache. It does not write the value to the | |||
// underlying database. Set does not incur a performance penalty compared to SetNoLock. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's all this about performance penalty? These functions lock a mutex while XxxNoLock does not
db/cache_db.go
Outdated
func (db *CacheDB) DeleteNoLock(key []byte) { | ||
db.cache[string(key)] = cDBValue{nil, true} | ||
} | ||
|
||
// Close closes the underlying database by deallocating the handle. Any attempts to use the DB | ||
// after close will result in a panic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably isn't true, maybe we should clarify that the db should not be used after Close. But it won't panic in this case I don't thiknk ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's right. It depends on the backing database.
.gitignore
Outdated
@@ -1,4 +1,5 @@ | |||
*.swp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -11,7 +11,7 @@ all: test | |||
NOVENDOR = go list github.com/tendermint/tmlibs/... | grep -v /vendor/ | |||
|
|||
test: | |||
go test `glide novendor` | |||
go test -tags gcc `glide novendor` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am not too sure if we should always be including the gcc tag, since on my Mac this is going to trip out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come it trips on your Mac? It runs fine on macOS 10.13 for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me... go 1.8.1, 10.12.x
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha nice, my Mac has issues with gcc tags but I'll try again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was where I first tripped out tendermint/merkleeyes#39 (comment) 2 months ago.
db/cache_db.go
Outdated
if ok { | ||
return value.value | ||
} | ||
return db.parent.Get(key) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't see where we memoize the cache-missed value for this, perhaps let's do this.
value, ok := db.cache[string(key)]
if !ok {
data := db.parent.Get(key)
value = cDBValue{value: data}
db.cache[string(key)] = value
}
return value.value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also should we perhaps check if deleted { return nil }
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering about that too. Maybe @jaekwon can clarify how this functionality is supposed to work. I got the understanding that you had to explicitly add values to the cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doh, yeah.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if deleted, value.value will be nil.
db/cache_db.go
Outdated
} | ||
|
||
// SetNoLock sets the value for the given key in the database. It does not write the value to the | ||
// underlying database. SetNoLock implements atomicSetDeleter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come we are referring to an atomicSetDeleter
unexported interface?
Perhaps could we just do a compile type assertion and not mention it? e.g
var _ atomicSetDeleter = (*CacheDB)(nil)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
db/cache_db.go
Outdated
// DeleteNoLock deletes the value for the given key. It does not delete the value from the | ||
// underlying database. DeleteNoLock implements atomicSetDeleter. | ||
func (db *CacheDB) DeleteNoLock(key []byte) { | ||
db.cache[string(key)] = cDBValue{nil, true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need mutex protection here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the function is called DeleteNoLock
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahah, thanks for that catch @cloudhead I made that suggestion without noticing that.
db/cache_db.go
Outdated
|
||
stats := make(map[string]string) | ||
stats["cache.size"] = fmt.Sprintf("%d", len(db.cache)) | ||
mergeStats(db.parent.Stats(), stats, "parent.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
|
||
// Clear the cache | ||
db.cache = make(map[string]cDBValue) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice routine!
db/mem_db.go
Outdated
delete(db.db, string(key)) | ||
} | ||
|
||
// Close is a noop. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the doc!
func (kvps KVPairs) Len() int { return len(kvps) } | ||
func (kvps KVPairs) Less(i, j int) bool { return kvps[i].Key < kvps[j].Key } | ||
func (kvps KVPairs) Swap(i, j int) { kvps[i], kvps[j] = kvps[j], kvps[i] } | ||
func (kvps KVPairs) Sort() { sort.Sort(kvps) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tagged you on the internal thread about the support for Go1.8+ where we'll later on just use sort.Slice
that is
sort.Slice(kvPairs, func(i, j int) bool {
kvi, kvj := kvPairs[i], kvPairs[j]
return kvi.Key < kvj.Key
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
iavl only compiles with go 1.8+
I questioned this, and bucky, alexis, and adrian all spoke in favor of making this a requirement. It is commented in cosmos-sdk (due to iavl dependency), let's just update everything to 1.8
merkle/simple_proof.go
Outdated
if computedHash == nil { | ||
return false | ||
} | ||
if !bytes.Equal(computedHash, rootHash) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return computedHash != nil && bytes.Equal(computedHash, rootHash)
} | ||
switch total { | ||
case 0: | ||
panic("Cannot call computeHashFromAunts() with 0 total") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And for a nerdy detour and if you get bored: this is one of my favorite compiler checks. We don't need the return nil below because panic
is a terminating statement :) as per
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @jaekwon for the PR, I've added a few suggestions, PTAL.
db/c_level_db.go
Outdated
} | ||
|
||
func (c cLevelDBIterator) Close() { | ||
c.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c.itr.Close()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jesus. thanks.
db/c_level_db.go
Outdated
} | ||
|
||
func (c cLevelDBIterator) GetError() error { | ||
return c.GetError() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
c.itr.GetError()
?
db/c_level_db_test.go
Outdated
@@ -7,7 +7,7 @@ import ( | |||
"fmt" | |||
"testing" | |||
|
|||
. "github.com/tendermint/tmlibs/common" | |||
"github.com/tendermint/tmlibs/common" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the convention is cmn
db/cache_db.go
Outdated
|
||
value, ok := db.cache[string(key)] | ||
if !ok { | ||
fmt.Println("1", string(key)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these debug statements?
db/common_test.go
Outdated
"github.com/tendermint/tmlibs/common" | ||
) | ||
|
||
func checkValid(t *testing.T, itr Iterator, expected bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❤️ these wrappers
db/common_test.go
Outdated
} | ||
|
||
func checkNextPanics(t *testing.T, itr Iterator) { | ||
defer func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://godoc.org/github.com/stretchr/testify/assert#Panics
assert.Panics(t, func(){ itr.Next() }, "Next expected panic but didn't")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yay
db/common_test.go
Outdated
} | ||
|
||
func checkPrevPanics(t *testing.T, itr Iterator) { | ||
defer func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same
db/common_test.go
Outdated
} | ||
|
||
func newTempDB(t *testing.T, backend string) (db DB) { | ||
dir, dirname := common.Tempdir(fmt.Sprintf("test_go_iterator")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for fmt.Sprintf
db/go_level_db.go
Outdated
} | ||
if !it.source.Valid() { | ||
it.invalid = true | ||
return false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it.invalid {
return false
}
it.invalid = !it.source.Valid()
return !it.invalid
db/util.go
Outdated
return pi.itr.Value() | ||
} | ||
|
||
func (pi *prefixIterator) Close() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't we delegate to pi.itr
? pi.itr.Close()
@@ -7,7 +7,7 @@ import ( | |||
"fmt" | |||
"testing" | |||
|
|||
. "github.com/tendermint/tmlibs/common" | |||
cmn "github.com/tendermint/tmlibs/common" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
db/cache_db.go
Outdated
defer db.mtx.Unlock() | ||
|
||
value, ok := db.cache[string(key)] | ||
if !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other check we might need, I had mentioned in the previous review but in later comments: we perhaps want to check that value.deleted is not set lest that's a discarded value in the cache i.e
if !ok || value.deleted {
}
What do y'all think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not value.deleted... if value.deleted in the cache, we don't want to query the base store.
if value.deleted, value.value is nil btw.
|
||
func (db *CacheDB) Set(key []byte, value []byte) { | ||
db.mtx.Lock() | ||
defer db.mtx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now here I am being a bit painful about this, but I believe we can squeeze even better perhaps by avoiding defers whenever we can, as they are ~2X slower than non-defers, please see https://gist.github.com/odeke-em/f8fb3a8695b33dd06a700fc3473f3e62.
Could we please make all these:
db.mtx.Lock()
db.SetNoLock(key, value)
db.mtx.Unlock()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I insist that we keep the defers until we find that it's a bottleneck... it's defensive programming that IMO is well worth it.
|
||
func (db *CacheDB) SetSync(key []byte, value []byte) { | ||
db.mtx.Lock() | ||
defer db.mtx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto about the defers
|
||
func (db *CacheDB) Delete(key []byte) { | ||
db.mtx.Lock() | ||
defer db.mtx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto about the defers
|
||
func (db *CacheDB) DeleteSync(key []byte) { | ||
db.mtx.Lock() | ||
defer db.mtx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto about the defers
|
||
func (db *CacheDB) Close() { | ||
db.mtx.Lock() | ||
defer db.mtx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto about the defers
db.parent.Close() | ||
} | ||
|
||
func (db *CacheDB) Print() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So while Print is useful from an external perspective, it is not that easy to test. IMHO we could do something like
func (db *CacheDB) Fprint(w io.Writer) {
db.mtx.Lock()
defer db.mtx.Unlock()
fmt.Fprintln(w, "CacheDB\ncache:")
for key, value := range db.cache {
fmt.Fprintf(w, "[%X]:\t[%v\n", key, value)
}
fmt.Fprintln(w, "\nparent:")
db.parent.Print()
}
func (db *CacheDB) Print() { db.Fprint(os.Stdout) }
and then properly test the order and expected out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't want to test Print functions TBH.
@@ -24,17 +25,59 @@ type Batch interface { | |||
Write() | |||
} | |||
|
|||
/* | |||
Usage: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me likey!
db/go_level_db_test.go
Outdated
@@ -6,7 +6,7 @@ import ( | |||
"fmt" | |||
"testing" | |||
|
|||
. "github.com/tendermint/tmlibs/common" | |||
"github.com/tendermint/tmlibs/common" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit, the convention seemingly used is cmn
instead of common
ie
cmn "github.com/tendermint/tmlibs/common"
db/mem_batch.go
Outdated
type opType int | ||
|
||
const ( | ||
opTypeSet = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We missed setting the opType here so
const (
opTypeSet opType = 1
opTypeDelete opType = 2
)
} | ||
|
||
func (mBatch *memBatch) Write() { | ||
mtx := mBatch.db.Mutex() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
mBatch.ops = append(mBatch.ops, operation{opTypeDelete, key, nil}) | ||
} | ||
|
||
func (mBatch *memBatch) Write() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Write? Flush perhaps? Am just bikeshedding on the name, it doesn't matter much :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean you're yak-shaving the lingo?
db/mem_db.go
Outdated
@@ -28,15 +29,30 @@ func (db *MemDB) Get(key []byte) []byte { | |||
return db.db[string(key)] | |||
} | |||
|
|||
func (db *MemDB) GetOK(key []byte) ([]byte, bool) { | |||
db.mtx.Lock() | |||
defer db.mtx.Unlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here about an unnecessary defer.
db/mem_db.go
Outdated
} | ||
|
||
func (db *MemDB) SetSync(key []byte, value []byte) { | ||
db.mtx.Lock() | ||
defer db.mtx.Unlock() | ||
db.SetNoLock(key, value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto here about the unnecessary defer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks @jaekwon for the round of review addressing. I just have one last set of requests then am gucci to approve, most especially about some defers that we could shed, PTAL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, it looks quite nice, and I like the design.
Two points about CacheWrap, returning something more useful than interface{} and separating read and write caches. Otherwise, I like it.
|
||
// Seek moves the iterator the position of the key given or, if the key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So seek is like the replacement for IteratorPrefix?
Can you add in the example a check to show how to iterate in a range? Cuz if we use seek to find the beginning, there is probably an end we also want to see.
But yeah, Seek() with adding Prev() as well as Next() seems like a nice generalization that allows a lot more flexibility to whoever uses this. Just show those example usages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the replacement for IteratorPrefix is db.IteratePrefix(db DB, prefix []byte) Iterator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides using db.IteratePrefix, You'll just have to manually stop iteration when you reach the end that you desire
Stats() map[string]string | ||
|
||
// CacheWrap wraps the DB w/ a CacheDB. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this return interface{}? It provides no guarantees.
In cachedb_test.go, I see this line often:
cdb := mem.CacheWrap().(*CacheDB)
If the only way to use it is to cast it to a particular type, then just force that type.
If you want people to be able to use the CacheWrap without forcing a given type, then return a usable interface,that exposes some subset of the DB interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it seems *CacheDB implements the entire DB interface, why not return it there? Or remove a method or two that you don't want to require from Caches...
type Cache interface {
.....
}
type DB interface {
Cache
NewBatch() Batch
Iterate() Iterator
}
then we could just use cdb := mem.CacheWrap()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe. But CacheWrap also exists in the SDK for the merkle stores, and those don't conform to tmlibs/db.DB...
require.Panics(t, func() { cdb2.Write() }) | ||
} | ||
|
||
func TestCacheDBNested(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good tests. Maybe add a few more.
What if we make two caches A, and B from one parent, and then try to write them both?
Is this allowed? Does it return an error? If it is allowed, what is supposed to happen?
Test cases are a great way to document and enforce this case.
db/cache_db.go
Outdated
} | ||
|
||
// CacheDB wraps an in-memory cache around an underlying DB. | ||
type CacheDB struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We really should have two caches, a read cache and a write cache... Otherwise, if we query a value, we write that to the db again. Not only is this wasteful use of IO, but with the version tree, this actually changes the hash.
https://github.com/cosmos/cosmos-sdk/blob/develop/state/kvcache.go#L6-L11
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixing so that we have a "dirty" flag.
* Add CacheDB & SimpleMap * Generic memBatch; Fix cLevelDB tests * CacheWrap() for CacheDB and MemDB * Change Iterator to match LeviGo Iterator * Fixes from review * cacheWrapWriteMutex and some race fixes * Use tmlibs/common * NewCWWMutex is exposed. DB can be CacheWrap'd * Remove GetOK, not needed * Fsdb (#72) * Add FSDB * Review fixes from Anton * Review changes * Fixes from review
This PR is to make the tendermint/tmlibs/db CacheWrap() compatible.
We could in the future use cosmos/cosmos-sdk/state/kvcache.go (or whatever it becomes), but it's not clear whether we want to make the Cosmos KVStore/IterKVStore interfaces (designed for Merkle stores) compatible with the Tendermint DB interface (designed for LevelDB etc). Until we can unify them lets keep them separate like this for now.
Also fixes a bug where MemDB keys weren't being ordered before Merkle-rooting.
Also fixes c-leveldb compatibility. You need to run
brew install leveldb
or equivalent to run the tests.NOTE: We need c-leveldb support so we should keep them as part of the tests, and fix our CI environment as necessary, instead of crippling our tests.