walletdb: add new Batch package-level function
In this commit, we add a new package-level function to emulate the existing Batch interface for bbolt. We do this via a new super-set interface which is then checked against in the main implementation of the Batch method.
This commit is contained in:
parent
da2fe0e3b0
commit
2c6a714c14
6 changed files with 122 additions and 2 deletions
1
go.sum
1
go.sum
|
@ -89,6 +89,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
|
|||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
@ -342,6 +342,19 @@ func (db *db) Close() error {
|
|||
return convertErr((*bbolt.DB)(db).Close())
|
||||
}
|
||||
|
||||
// Batch is similar to the package-level Update method, but it will attempt to
|
||||
// optismitcally combine the invocation of several transaction functions into a
|
||||
// single db write transaction.
|
||||
//
|
||||
// This function is part of the walletdb.Db interface implementation.
|
||||
func (db *db) Batch(f func(tx walletdb.ReadWriteTx) error) error {
|
||||
return (*bbolt.DB)(db).Batch(func(btx *bbolt.Tx) error {
|
||||
interfaceTx := transaction{btx}
|
||||
|
||||
return f(&interfaceTx)
|
||||
})
|
||||
}
|
||||
|
||||
// filesExists reports whether the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
|
|
|
@ -7,5 +7,6 @@ require (
|
|||
github.com/coreos/bbolt v1.3.3
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect
|
||||
)
|
||||
|
|
|
@ -6,5 +6,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
|
||||
package walletdb
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadTx represents a database transaction that can only be used for reads. If
|
||||
// a database update must occur, use a ReadWriteTx.
|
||||
|
@ -201,6 +204,18 @@ type DB interface {
|
|||
Close() error
|
||||
}
|
||||
|
||||
// BatchDB is a special version of the main DB interface that allos the caller
|
||||
// to specify write transactions that should be combine dtoegether if multiple
|
||||
// goroutines are calling the Batch method.
|
||||
type BatchDB interface {
|
||||
DB
|
||||
|
||||
// Batch is similar to the package-level Update method, but it will
|
||||
// attempt to optismitcally combine the invocation of several
|
||||
// transaction functions into a single db write transaction.
|
||||
Batch(func(tx ReadWriteTx) error) error
|
||||
}
|
||||
|
||||
// View opens a database read transaction and executes the function f with the
|
||||
// transaction passed as a parameter. After f exits, the transaction is rolled
|
||||
// back. If f errors, its error is returned, not a rollback error (if any
|
||||
|
@ -260,6 +275,23 @@ func Update(db DB, f func(tx ReadWriteTx) error) error {
|
|||
return tx.Commit()
|
||||
}
|
||||
|
||||
// Batch opens a database read/write transaction and executes the function f
|
||||
// with the transaction passed as a parameter. After f exits, if f did not
|
||||
// error, the transaction is committed. Otherwise, if f did error, the
|
||||
// transaction is rolled back. If the rollback fails, the original error
|
||||
// returned by f is still returned. If the commit fails, the commit error is
|
||||
// returned.
|
||||
//
|
||||
// Batch is only useful when there are multiple goroutines calling it.
|
||||
func Batch(db DB, f func(tx ReadWriteTx) error) error {
|
||||
batchDB, ok := db.(BatchDB)
|
||||
if !ok {
|
||||
return fmt.Errorf("need batch")
|
||||
}
|
||||
|
||||
return batchDB.Batch(f)
|
||||
}
|
||||
|
||||
// Driver defines a structure for backend drivers to use when they registered
|
||||
// themselves as a backend which implements the Db interface.
|
||||
type Driver struct {
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
package walletdbtest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcwallet/walletdb"
|
||||
)
|
||||
|
@ -146,7 +148,7 @@ func testSequence(tc *testContext, testBucket walletdb.ReadWriteBucket) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
// testReadWriteBucketInterface ensures the bucket interface is working properly by
|
||||
|
@ -726,6 +728,70 @@ func testAdditionalErrors(tc *testContext) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// testBatchInterface tests that if the target database implements the batch
|
||||
// method, then the method functions as expected.
|
||||
func testBatchInterface(tc *testContext) bool {
|
||||
// If the database doesn't support the batch super-set of the
|
||||
// interface, then we're done here.
|
||||
batchDB, ok := tc.db.(walletdb.BatchDB)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
const numGoroutines = 5
|
||||
errChan := make(chan error, numGoroutines)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
err := walletdb.Batch(batchDB, func(tx walletdb.ReadWriteTx) error {
|
||||
b, err := tx.CreateTopLevelBucket([]byte("test"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
byteI := []byte{byte(i)}
|
||||
return b.Put(byteI, byteI)
|
||||
})
|
||||
errChan <- err
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
tc.t.Errorf("Batch: unexpected error: %v", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
err := walletdb.View(batchDB, func(tx walletdb.ReadTx) error {
|
||||
b := tx.ReadBucket([]byte("test"))
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
byteI := []byte{byte(i)}
|
||||
if v := b.Get(byteI); v == nil {
|
||||
return fmt.Errorf("key %v not present", byteI)
|
||||
} else if !bytes.Equal(v, byteI) {
|
||||
return fmt.Errorf("key %v not equal to value: "+
|
||||
"%v", byteI, v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
tc.t.Errorf("Batch: unexpected error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// TestInterface performs all interfaces tests for this database driver.
|
||||
func TestInterface(t Tester, dbType, dbPath string) {
|
||||
db, err := walletdb.Create(dbType, dbPath, true)
|
||||
|
@ -754,4 +820,9 @@ func TestInterface(t Tester, dbType, dbPath string) {
|
|||
if !testAdditionalErrors(&context) {
|
||||
return
|
||||
}
|
||||
|
||||
// If applicable, also test the behavior of the Batch call.
|
||||
if !testBatchInterface(&context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue