vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood 2018-01-16 13:20:59 +00:00
parent 8e83fb6fb9
commit 7d3a17725d
4878 changed files with 1974229 additions and 201215 deletions

View file

@ -2,3 +2,4 @@
*.test
*.swp
/bin/
cmd/bolt/bolt

View file

@ -7,13 +7,16 @@ default: build
race:
@go test -v -race -test.run="TestSimulate_(100op|1000op)"
# go get honnef.co/go/tools/simple
# go get honnef.co/go/tools/unused
fmt:
gosimple ./...
unused ./...
gofmt -l -s -d $(find -name \*.go)
!(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]')
# go get honnef.co/go/tools/simple
gosimple:
gosimple ./...
# go get honnef.co/go/tools/unused
unused:
unused ./...
# go get github.com/kisielk/errcheck
errcheck:
@ -24,4 +27,4 @@ test:
# Note: gets "program not an importable package" in out of path builds
go test -v ./cmd/bolt
.PHONY: race fmt errcheck test
.PHONY: race fmt errcheck test gosimple unused

View file

@ -5,7 +5,7 @@ bbolt
[![Coverage](https://codecov.io/gh/coreos/bbolt/branch/master/graph/badge.svg)](https://codecov.io/gh/coreos/bbolt)
[![Godoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/coreos/bbolt)
bbolt is a fork of [Ben Johnson's][gh_ben] moribund [Bolt][bolt] key/value
bbolt is a fork of [Ben Johnson's][gh_ben] [Bolt][bolt] key/value
store. The purpose of this fork is to provide the Go community with an active
maintenance and development target for Bolt; the goal is improved reliability
and stability. bbolt includes bug fixes, performance enhancements, and features
@ -919,6 +919,7 @@ Below is a list of public, open source projects that use Bolt:
* [torrent](https://github.com/anacrolix/torrent) - Full-featured BitTorrent client package and utilities in Go. BoltDB is a storage backend in development.
* [gopherpit](https://github.com/gopherpit/gopherpit) - A web service to manage Go remote import paths with custom domains
* [bolter](https://github.com/hasit/bolter) - Command-line app for viewing BoltDB file in your terminal.
* [boltcli](https://github.com/spacewander/boltcli) - the redis-cli for boltdb with Lua script support.
* [btcwallet](https://github.com/btcsuite/btcwallet) - A bitcoin wallet.
* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency.
* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies

30
vendor/github.com/coreos/bbolt/allocate_test.go generated vendored Normal file
View file

@ -0,0 +1,30 @@
package bolt
import (
"testing"
)
func TestTx_allocatePageStats(t *testing.T) {
f := newFreelist()
f.ids = []pgid{2, 3}
tx := &Tx{
db: &DB{
freelist: f,
pageSize: defaultPageSize,
},
meta: &meta{},
pages: make(map[pgid]*page),
}
prePageCnt := tx.Stats().PageCount
allocateCnt := f.free_count()
if _, err := tx.allocate(allocateCnt); err != nil {
t.Fatal(err)
}
if tx.Stats().PageCount != prePageCnt+allocateCnt {
t.Errorf("Allocated %d but got %d page in stats", allocateCnt, tx.Stats().PageCount)
}
}

View file

@ -13,29 +13,32 @@ import (
// flock acquires an advisory lock on a file descriptor.
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
var t time.Time
if timeout != 0 {
t = time.Now()
}
fd := db.file.Fd()
flag := syscall.LOCK_NB
if exclusive {
flag |= syscall.LOCK_EX
} else {
flag |= syscall.LOCK_SH
}
for {
// If we're beyond our timeout then return an error.
// This can only occur after we've attempted a flock once.
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
flag := syscall.LOCK_SH
if exclusive {
flag = syscall.LOCK_EX
}
// Otherwise attempt to obtain an exclusive lock.
err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
// Attempt to obtain an exclusive lock.
err := syscall.Flock(int(fd), flag)
if err == nil {
return nil
} else if err != syscall.EWOULDBLOCK {
return err
}
// If we timed out then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout
}
// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
time.Sleep(flockRetryTimeout)
}
}

View file

@ -13,34 +13,33 @@ import (
// flock acquires an advisory lock on a file descriptor.
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
var t time.Time
if timeout != 0 {
t = time.Now()
}
fd := db.file.Fd()
var lockType int16
if exclusive {
lockType = syscall.F_WRLCK
} else {
lockType = syscall.F_RDLCK
}
for {
// If we're beyond our timeout then return an error.
// This can only occur after we've attempted a flock once.
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Pid = 0
lock.Whence = 0
lock.Pid = 0
if exclusive {
lock.Type = syscall.F_WRLCK
} else {
lock.Type = syscall.F_RDLCK
}
err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
// Attempt to obtain an exclusive lock.
lock := syscall.Flock_t{Type: lockType}
err := syscall.FcntlFlock(fd, syscall.F_SETLK, &lock)
if err == nil {
return nil
} else if err != syscall.EAGAIN {
return err
}
// If we timed out then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout
}
// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
time.Sleep(flockRetryTimeout)
}
}

View file

@ -59,29 +59,30 @@ func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) erro
db.lockfile = f
var t time.Time
if timeout != 0 {
t = time.Now()
}
fd := f.Fd()
var flag uint32 = flagLockFailImmediately
if exclusive {
flag |= flagLockExclusive
}
for {
// If we're beyond our timeout then return an error.
// This can only occur after we've attempted a flock once.
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
var flag uint32 = flagLockFailImmediately
if exclusive {
flag |= flagLockExclusive
}
err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
// Attempt to obtain an exclusive lock.
err := lockFileEx(syscall.Handle(fd), flag, 0, 1, 0, &syscall.Overlapped{})
if err == nil {
return nil
} else if err != errLockViolation {
return err
}
// If we timed oumercit then return an error.
if timeout != 0 && time.Since(t) > timeout-flockRetryTimeout {
return ErrTimeout
}
// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
time.Sleep(flockRetryTimeout)
}
}

View file

@ -114,6 +114,8 @@ func (m *Main) Run(args ...string) error {
return newCompactCommand(m).Run(args[1:]...)
case "dump":
return newDumpCommand(m).Run(args[1:]...)
case "page-item":
return newPageItemCommand(m).Run(args[1:]...)
case "get":
return newGetCommand(m).Run(args[1:]...)
case "info":
@ -153,6 +155,7 @@ The commands are:
help print this screen
page print one or more pages in human readable format
pages print list of pages with their types
page-item print the key and value of a page item.
stats iterate over all pages and generate usage stats
Use "bolt [command] -h" for more information about a command.
@ -416,9 +419,173 @@ func (cmd *DumpCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSi
// Usage returns the help message.
func (cmd *DumpCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt dump -page PAGEID PATH
usage: bolt dump PATH pageid [pageid...]
Dump prints a hexadecimal dump of a single page.
Dump prints a hexadecimal dump of one or more pages.
`, "\n")
}
// PageItemCommand represents the "page-item" command execution.
type PageItemCommand struct {
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// newPageItemCommand returns a PageItemCommand.
func newPageItemCommand(m *Main) *PageItemCommand {
return &PageItemCommand{
Stdin: m.Stdin,
Stdout: m.Stdout,
Stderr: m.Stderr,
}
}
type pageItemOptions struct {
help bool
keyOnly bool
valueOnly bool
format string
}
// Run executes the command.
func (cmd *PageItemCommand) Run(args ...string) error {
// Parse flags.
options := &pageItemOptions{}
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.BoolVar(&options.keyOnly, "key-only", false, "Print only the key")
fs.BoolVar(&options.valueOnly, "value-only", false, "Print only the value")
fs.StringVar(&options.format, "format", "ascii-encoded", "Output format. One of: ascii-encoded|hex|bytes")
fs.BoolVar(&options.help, "h", false, "")
if err := fs.Parse(args); err != nil {
return err
} else if options.help {
fmt.Fprintln(cmd.Stderr, cmd.Usage())
return ErrUsage
}
if options.keyOnly && options.valueOnly {
return fmt.Errorf("The --key-only or --value-only flag may be set, but not both.")
}
// Require database path and page id.
path := fs.Arg(0)
if path == "" {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
}
// Read page id.
pageID, err := strconv.Atoi(fs.Arg(1))
if err != nil {
return err
}
// Read item id.
itemID, err := strconv.Atoi(fs.Arg(2))
if err != nil {
return err
}
// Open database file handler.
f, err := os.Open(path)
if err != nil {
return err
}
defer func() { _ = f.Close() }()
// Retrieve page info and page size.
_, buf, err := ReadPage(path, pageID)
if err != nil {
return err
}
if !options.valueOnly {
err := cmd.PrintLeafItemKey(cmd.Stdout, buf, uint16(itemID), options.format)
if err != nil {
return err
}
}
if !options.keyOnly {
err := cmd.PrintLeafItemValue(cmd.Stdout, buf, uint16(itemID), options.format)
if err != nil {
return err
}
}
return nil
}
// leafPageElement retrieves a leaf page element.
func (cmd *PageItemCommand) leafPageElement(pageBytes []byte, index uint16) (*leafPageElement, error) {
p := (*page)(unsafe.Pointer(&pageBytes[0]))
if index >= p.count {
return nil, fmt.Errorf("leafPageElement: expected item index less than %d, but got %d.", p.count, index)
}
if p.Type() != "leaf" {
return nil, fmt.Errorf("leafPageElement: expected page type of 'leaf', but got '%s'", p.Type())
}
return p.leafPageElement(index), nil
}
// writeBytes writes the byte to the writer. Supported formats: ascii-encoded, hex, bytes.
func (cmd *PageItemCommand) writeBytes(w io.Writer, b []byte, format string) error {
switch format {
case "ascii-encoded":
_, err := fmt.Fprintf(w, "%q", b)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, "\n")
return err
case "hex":
_, err := fmt.Fprintf(w, "%x", b)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, "\n")
return err
case "bytes":
_, err := w.Write(b)
return err
default:
return fmt.Errorf("writeBytes: unsupported format: %s", format)
}
}
// PrintLeafItemKey writes the bytes of a leaf element's key.
func (cmd *PageItemCommand) PrintLeafItemKey(w io.Writer, pageBytes []byte, index uint16, format string) error {
e, err := cmd.leafPageElement(pageBytes, index)
if err != nil {
return err
}
return cmd.writeBytes(w, e.key(), format)
}
// PrintLeafItemKey writes the bytes of a leaf element's value.
func (cmd *PageItemCommand) PrintLeafItemValue(w io.Writer, pageBytes []byte, index uint16, format string) error {
e, err := cmd.leafPageElement(pageBytes, index)
if err != nil {
return err
}
return cmd.writeBytes(w, e.value(), format)
}
// Usage returns the help message.
func (cmd *PageItemCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt page-item [options] PATH pageid itemid
Additional options include:
--key-only
Print only the key
--value-only
Print only the value
--format
Output format. One of: ascii-encoded|hex|bytes (default=ascii-encoded)
page-item prints a page item key and value.
`, "\n")
}
@ -592,13 +759,22 @@ func (cmd *PageCommand) PrintBranch(w io.Writer, buf []byte) error {
func (cmd *PageCommand) PrintFreelist(w io.Writer, buf []byte) error {
p := (*page)(unsafe.Pointer(&buf[0]))
// Check for overflow and, if present, adjust starting index and actual element count.
idx, count := 0, int(p.count)
if p.count == 0xFFFF {
idx = 1
count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0])
}
// Print number of items.
fmt.Fprintf(w, "Item Count: %d\n", p.count)
fmt.Fprintf(w, "Item Count: %d\n", count)
fmt.Fprintf(w, "Overflow: %d\n", p.overflow)
fmt.Fprintf(w, "\n")
// Print each page in the freelist.
ids := (*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr))
for i := uint16(0); i < p.count; i++ {
for i := int(idx); i < count; i++ {
fmt.Fprintf(w, "%d\n", ids[i])
}
fmt.Fprintf(w, "\n")
@ -653,7 +829,7 @@ func (cmd *PageCommand) PrintPage(w io.Writer, r io.ReaderAt, pageID int, pageSi
// Usage returns the help message.
func (cmd *PageCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt page -page PATH pageid [pageid...]
usage: bolt page PATH pageid [pageid...]
Page prints one or more pages in human readable format.
`, "\n")

56
vendor/github.com/coreos/bbolt/db.go generated vendored
View file

@ -40,6 +40,9 @@ const (
// default page size for db is set to the OS page size.
var defaultPageSize = os.Getpagesize()
// The time elapsed between consecutive file locking attempts.
const flockRetryTimeout = 50 * time.Millisecond
// DB represents a collection of buckets persisted to a file on disk.
// All data access is performed through transactions which can be obtained through the DB.
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
@ -113,9 +116,11 @@ type DB struct {
opened bool
rwtx *Tx
txs []*Tx
freelist *freelist
stats Stats
freelist *freelist
freelistLoad sync.Once
pagePool sync.Pool
batchMu sync.Mutex
@ -154,8 +159,9 @@ func (db *DB) String() string {
// If the file does not exist then it will be created automatically.
// Passing in nil options will cause Bolt to open the database with the default options.
func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
var db = &DB{opened: true}
db := &DB{
opened: true,
}
// Set default options if no options are provided.
if options == nil {
options = DefaultOptions
@ -251,20 +257,11 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
return db, nil
}
db.freelist = newFreelist()
noFreeList := db.meta().freelist == pgidNoFreelist
if noFreeList {
// Reconstruct free list by scanning the DB.
db.freelist.readIDs(db.freepages())
} else {
// Read free list from freelist page.
db.freelist.read(db.page(db.meta().freelist))
}
db.stats.FreePageN = len(db.freelist.ids)
db.loadFreelist()
// Flush freelist when transitioning from no sync to sync so
// NoFreelistSync unaware boltdb can open the db later.
if !db.NoFreelistSync && noFreeList {
if !db.NoFreelistSync && !db.hasSyncedFreelist() {
tx, err := db.Begin(true)
if tx != nil {
err = tx.Commit()
@ -279,6 +276,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
return db, nil
}
// loadFreelist reads the freelist if it is synced, or reconstructs it
// by scanning the DB if it is not synced. It assumes there are no
// concurrent accesses being made to the freelist.
func (db *DB) loadFreelist() {
db.freelistLoad.Do(func() {
db.freelist = newFreelist()
if !db.hasSyncedFreelist() {
// Reconstruct free list by scanning the DB.
db.freelist.readIDs(db.freepages())
} else {
// Read free list from freelist page.
db.freelist.read(db.page(db.meta().freelist))
}
db.stats.FreePageN = len(db.freelist.ids)
})
}
func (db *DB) hasSyncedFreelist() bool {
return db.meta().freelist != pgidNoFreelist
}
// mmap opens the underlying memory-mapped file and initializes the meta references.
// minsz is the minimum size that the new mmap can be.
func (db *DB) mmap(minsz int) error {
@ -684,11 +702,7 @@ func (db *DB) View(fn func(*Tx) error) error {
return err
}
if err := t.Rollback(); err != nil {
return err
}
return nil
return t.Rollback()
}
// Batch calls fn as part of a batch. It behaves similar to Update,
@ -788,9 +802,7 @@ retry:
// pass success, or bolt internal errors, to all callers
for _, c := range b.calls {
if c.err != nil {
c.err <- err
}
c.err <- err
}
break retry
}

View file

@ -9,6 +9,7 @@ import (
"hash/fnv"
"io/ioutil"
"log"
"math/rand"
"os"
"path/filepath"
"regexp"
@ -44,6 +45,8 @@ type meta struct {
// Ensure that a database can be opened without error.
func TestOpen(t *testing.T) {
path := tempfile()
defer os.RemoveAll(path)
db, err := bolt.Open(path, 0666, nil)
if err != nil {
t.Fatal(err)
@ -79,6 +82,7 @@ func TestOpen_ErrNotExists(t *testing.T) {
// Ensure that opening a file that is not a Bolt database returns ErrInvalid.
func TestOpen_ErrInvalid(t *testing.T) {
path := tempfile()
defer os.RemoveAll(path)
f, err := os.Create(path)
if err != nil {
@ -90,7 +94,6 @@ func TestOpen_ErrInvalid(t *testing.T) {
if err := f.Close(); err != nil {
t.Fatal(err)
}
defer os.Remove(path)
if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrInvalid {
t.Fatalf("unexpected error: %s", err)
@ -302,15 +305,16 @@ func TestOpen_Size_Large(t *testing.T) {
// Ensure that a re-opened database is consistent.
func TestOpen_Check(t *testing.T) {
path := tempfile()
defer os.RemoveAll(path)
db, err := bolt.Open(path, 0666, nil)
if err != nil {
t.Fatal(err)
}
if err := db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
if err = db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
t.Fatal(err)
}
if err := db.Close(); err != nil {
if err = db.Close(); err != nil {
t.Fatal(err)
}
@ -334,18 +338,19 @@ func TestOpen_MetaInitWriteError(t *testing.T) {
// Ensure that a database that is too small returns an error.
func TestOpen_FileTooSmall(t *testing.T) {
path := tempfile()
defer os.RemoveAll(path)
db, err := bolt.Open(path, 0666, nil)
if err != nil {
t.Fatal(err)
}
pageSize := int64(db.Info().PageSize)
if err := db.Close(); err != nil {
if err = db.Close(); err != nil {
t.Fatal(err)
}
// corrupt the database
if err := os.Truncate(path, pageSize); err != nil {
if err = os.Truncate(path, pageSize); err != nil {
t.Fatal(err)
}
@ -415,6 +420,60 @@ func TestDB_Open_InitialMmapSize(t *testing.T) {
}
}
// TestDB_Open_ReadOnly checks a database in read only mode can read but not write.
func TestDB_Open_ReadOnly(t *testing.T) {
// Create a writable db, write k-v and close it.
db := MustOpenDB()
defer db.MustClose()
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.DB.Close(); err != nil {
t.Fatal(err)
}
f := db.f
o := &bolt.Options{ReadOnly: true}
readOnlyDB, err := bolt.Open(f, 0666, o)
if err != nil {
panic(err)
}
if !readOnlyDB.IsReadOnly() {
t.Fatal("expect db in read only mode")
}
// Read from a read-only transaction.
if err := readOnlyDB.View(func(tx *bolt.Tx) error {
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
if !bytes.Equal(value, []byte("bar")) {
t.Fatal("expect value 'bar', got", value)
}
return nil
}); err != nil {
t.Fatal(err)
}
// Can't launch read-write transaction.
if _, err := readOnlyDB.Begin(true); err != bolt.ErrDatabaseReadOnly {
t.Fatalf("unexpected error: %s", err)
}
if err := readOnlyDB.Close(); err != nil {
t.Fatal(err)
}
}
// TestOpen_BigPage checks the database uses bigger pages when
// changing PageSize.
func TestOpen_BigPage(t *testing.T) {
@ -454,7 +513,7 @@ func TestOpen_RecoverFreeList(t *testing.T) {
t.Fatal(err)
}
}
if err := tx.Commit(); err != nil {
if err = tx.Commit(); err != nil {
t.Fatal(err)
}
@ -530,6 +589,65 @@ func TestDB_BeginRW(t *testing.T) {
}
}
// TestDB_Concurrent_WriteTo checks that issuing WriteTo operations concurrently
// with commits does not produce corrupted db files.
func TestDB_Concurrent_WriteTo(t *testing.T) {
o := &bolt.Options{NoFreelistSync: false}
db := MustOpenWithOption(o)
defer db.MustClose()
var wg sync.WaitGroup
wtxs, rtxs := 5, 5
wg.Add(wtxs * rtxs)
f := func(tx *bolt.Tx) {
defer wg.Done()
f, err := ioutil.TempFile("", "bolt-")
if err != nil {
panic(err)
}
time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond)
tx.WriteTo(f)
tx.Rollback()
f.Close()
snap := &DB{nil, f.Name(), o}
snap.MustReopen()
defer snap.MustClose()
snap.MustCheck()
}
tx1, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
if _, err := tx1.CreateBucket([]byte("abc")); err != nil {
t.Fatal(err)
}
if err := tx1.Commit(); err != nil {
t.Fatal(err)
}
for i := 0; i < wtxs; i++ {
tx, err := db.Begin(true)
if err != nil {
t.Fatal(err)
}
if err := tx.Bucket([]byte("abc")).Put([]byte{0}, []byte{0}); err != nil {
t.Fatal(err)
}
for j := 0; j < rtxs; j++ {
rtx, rerr := db.Begin(false)
if rerr != nil {
t.Fatal(rerr)
}
go f(rtx)
}
if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}
wg.Wait()
}
// Ensure that opening a transaction while the DB is closed returns an error.
func TestDB_BeginRW_Closed(t *testing.T) {
var db bolt.DB
@ -1217,7 +1335,7 @@ func ExampleDB_Begin_ReadOnly() {
defer os.Remove(db.Path())
// Create a bucket using a read-write transaction.
if err := db.Update(func(tx *bolt.Tx) error {
if err = db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucket([]byte("widgets"))
return err
}); err != nil {
@ -1230,16 +1348,16 @@ func ExampleDB_Begin_ReadOnly() {
log.Fatal(err)
}
b := tx.Bucket([]byte("widgets"))
if err := b.Put([]byte("john"), []byte("blue")); err != nil {
if err = b.Put([]byte("john"), []byte("blue")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("abby"), []byte("red")); err != nil {
if err = b.Put([]byte("abby"), []byte("red")); err != nil {
log.Fatal(err)
}
if err := b.Put([]byte("zephyr"), []byte("purple")); err != nil {
if err = b.Put([]byte("zephyr"), []byte("purple")); err != nil {
log.Fatal(err)
}
if err := tx.Commit(); err != nil {
if err = tx.Commit(); err != nil {
log.Fatal(err)
}
@ -1253,11 +1371,11 @@ func ExampleDB_Begin_ReadOnly() {
fmt.Printf("%s likes %s\n", k, v)
}
if err := tx.Rollback(); err != nil {
if err = tx.Rollback(); err != nil {
log.Fatal(err)
}
if err := db.Close(); err != nil {
if err = db.Close(); err != nil {
log.Fatal(err)
}
@ -1449,15 +1567,7 @@ type DB struct {
// MustOpenDB returns a new, open DB at a temporary location.
func MustOpenDB() *DB {
f := tempfile()
db, err := bolt.Open(f, 0666, nil)
if err != nil {
panic(err)
}
return &DB{
DB: db,
f: f,
}
return MustOpenWithOption(nil)
}
// MustOpenDBWithOption returns a new, open DB at a temporary location with given options.

View file

@ -132,9 +132,9 @@ func (f *freelist) free(txid txid, p *page) {
allocTxid, ok := f.allocs[p.id]
if ok {
delete(f.allocs, p.id)
} else if (p.flags & (freelistPageFlag | metaPageFlag)) != 0 {
// Safe to claim txid as allocating since these types are private to txid.
allocTxid = txid
} else if (p.flags & freelistPageFlag) != 0 {
// Freelist is always allocated by prior tx.
allocTxid = txid - 1
}
for id := p.id; id <= p.id+pgid(p.overflow); id++ {
@ -233,6 +233,9 @@ func (f *freelist) freed(pgid pgid) bool {
// read initializes the freelist from a freelist page.
func (f *freelist) read(p *page) {
if (p.flags & freelistPageFlag) == 0 {
panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ()))
}
// If the page.count is at the max uint16 value (64k) then it's considered
// an overflow and the size of the freelist is stored as the first element.
idx, count := 0, int(p.count)

View file

@ -44,6 +44,134 @@ func TestFreelist_release(t *testing.T) {
}
}
// Ensure that releaseRange handles boundary conditions correctly
func TestFreelist_releaseRange(t *testing.T) {
type testRange struct {
begin, end txid
}
type testPage struct {
id pgid
n int
allocTxn txid
freeTxn txid
}
var releaseRangeTests = []struct {
title string
pagesIn []testPage
releaseRanges []testRange
wantFree []pgid
}{
{
title: "Single pending in range",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
releaseRanges: []testRange{{1, 300}},
wantFree: []pgid{3},
},
{
title: "Single pending with minimum end range",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
releaseRanges: []testRange{{1, 200}},
wantFree: []pgid{3},
},
{
title: "Single pending outsize minimum end range",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
releaseRanges: []testRange{{1, 199}},
wantFree: nil,
},
{
title: "Single pending with minimum begin range",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
releaseRanges: []testRange{{100, 300}},
wantFree: []pgid{3},
},
{
title: "Single pending outside minimum begin range",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 100, freeTxn: 200}},
releaseRanges: []testRange{{101, 300}},
wantFree: nil,
},
{
title: "Single pending in minimum range",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
releaseRanges: []testRange{{199, 200}},
wantFree: []pgid{3},
},
{
title: "Single pending and read transaction at 199",
pagesIn: []testPage{{id: 3, n: 1, allocTxn: 199, freeTxn: 200}},
releaseRanges: []testRange{{100, 198}, {200, 300}},
wantFree: nil,
},
{
title: "Adjacent pending and read transactions at 199, 200",
pagesIn: []testPage{
{id: 3, n: 1, allocTxn: 199, freeTxn: 200},
{id: 4, n: 1, allocTxn: 200, freeTxn: 201},
},
releaseRanges: []testRange{
{100, 198},
{200, 199}, // Simulate the ranges db.freePages might produce.
{201, 300},
},
wantFree: nil,
},
{
title: "Out of order ranges",
pagesIn: []testPage{
{id: 3, n: 1, allocTxn: 199, freeTxn: 200},
{id: 4, n: 1, allocTxn: 200, freeTxn: 201},
},
releaseRanges: []testRange{
{201, 199},
{201, 200},
{200, 200},
},
wantFree: nil,
},
{
title: "Multiple pending, read transaction at 150",
pagesIn: []testPage{
{id: 3, n: 1, allocTxn: 100, freeTxn: 200},
{id: 4, n: 1, allocTxn: 100, freeTxn: 125},
{id: 5, n: 1, allocTxn: 125, freeTxn: 150},
{id: 6, n: 1, allocTxn: 125, freeTxn: 175},
{id: 7, n: 2, allocTxn: 150, freeTxn: 175},
{id: 9, n: 2, allocTxn: 175, freeTxn: 200},
},
releaseRanges: []testRange{{50, 149}, {151, 300}},
wantFree: []pgid{4, 9},
},
}
for _, c := range releaseRangeTests {
f := newFreelist()
for _, p := range c.pagesIn {
for i := uint64(0); i < uint64(p.n); i++ {
f.ids = append(f.ids, pgid(uint64(p.id)+i))
}
}
for _, p := range c.pagesIn {
f.allocate(p.allocTxn, p.n)
}
for _, p := range c.pagesIn {
f.free(p.freeTxn, &page{id: p.id})
}
for _, r := range c.releaseRanges {
f.releaseRange(r.begin, r.end)
}
if exp := c.wantFree; !reflect.DeepEqual(exp, f.ids) {
t.Errorf("exp=%v; got=%v for %s", exp, f.ids, c.title)
}
}
}
// Ensure that a freelist can find contiguous blocks of pages.
func TestFreelist_allocate(t *testing.T) {
f := newFreelist()

15
vendor/github.com/coreos/bbolt/tx.go generated vendored
View file

@ -317,7 +317,11 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
if err != nil {
return 0, err
}
defer func() { _ = f.Close() }()
defer func() {
if cerr := f.Close(); err == nil {
err = cerr
}
}()
// Generate a meta page. We use the same page data for both meta pages.
buf := make([]byte, tx.db.pageSize)
@ -345,7 +349,7 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
}
// Move past the meta pages in the file.
if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil {
if _, err := f.Seek(int64(tx.db.pageSize*2), io.SeekStart); err != nil {
return n, fmt.Errorf("seek: %s", err)
}
@ -356,7 +360,7 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
return n, err
}
return n, f.Close()
return n, nil
}
// CopyFile copies the entire database to file at the given path.
@ -391,6 +395,9 @@ func (tx *Tx) Check() <-chan error {
}
func (tx *Tx) check(ch chan error) {
// Force loading free list if opened in ReadOnly mode.
tx.db.loadFreelist()
// Check if any pages are double freed.
freed := make(map[pgid]bool)
all := make([]pgid, tx.db.freelist.count())
@ -476,7 +483,7 @@ func (tx *Tx) allocate(count int) (*page, error) {
tx.pages[p.id] = p
// Update statistics.
tx.stats.PageCount++
tx.stats.PageCount += count
tx.stats.PageAlloc += count * tx.db.pageSize
return p, nil

View file

@ -11,6 +11,54 @@ import (
"github.com/coreos/bbolt"
)
// TestTx_Check_ReadOnly tests consistency checking on a ReadOnly database.
func TestTx_Check_ReadOnly(t *testing.T) {
db := MustOpenDB()
defer db.Close()
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucket([]byte("widgets"))
if err != nil {
t.Fatal(err)
}
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
t.Fatal(err)
}
return nil
}); err != nil {
t.Fatal(err)
}
if err := db.DB.Close(); err != nil {
t.Fatal(err)
}
readOnlyDB, err := bolt.Open(db.f, 0666, &bolt.Options{ReadOnly: true})
if err != nil {
t.Fatal(err)
}
defer readOnlyDB.Close()
tx, err := readOnlyDB.Begin(false)
if err != nil {
t.Fatal(err)
}
// ReadOnly DB will load freelist on Check call.
numChecks := 2
errc := make(chan error, numChecks)
check := func() {
err, _ := <-tx.Check()
errc <- err
}
// Ensure the freelist is not reloaded and does not race.
for i := 0; i < numChecks; i++ {
go check()
}
for i := 0; i < numChecks; i++ {
if err := <-errc; err != nil {
t.Fatal(err)
}
}
}
// Ensure that committing a closed transaction returns an error.
func TestTx_Commit_ErrTxClosed(t *testing.T) {
db := MustOpenDB()
@ -602,6 +650,111 @@ func TestTx_CopyFile_Error_Normal(t *testing.T) {
}
}
// TestTx_releaseRange ensures db.freePages handles page releases
// correctly when there are transaction that are no longer reachable
// via any read/write transactions and are "between" ongoing read
// transactions, which requires they must be freed by
// freelist.releaseRange.
func TestTx_releaseRange(t *testing.T) {
// Set initial mmap size well beyond the limit we will hit in this
// test, since we are testing with long running read transactions
// and will deadlock if db.grow is triggered.
db := MustOpenWithOption(&bolt.Options{InitialMmapSize: os.Getpagesize() * 100})
defer db.MustClose()
bucket := "bucket"
put := func(key, value string) {
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(bucket))
if err != nil {
t.Fatal(err)
}
return b.Put([]byte(key), []byte(value))
}); err != nil {
t.Fatal(err)
}
}
del := func(key string) {
if err := db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(bucket))
if err != nil {
t.Fatal(err)
}
return b.Delete([]byte(key))
}); err != nil {
t.Fatal(err)
}
}
getWithTxn := func(txn *bolt.Tx, key string) []byte {
return txn.Bucket([]byte(bucket)).Get([]byte(key))
}
openReadTxn := func() *bolt.Tx {
readTx, err := db.Begin(false)
if err != nil {
t.Fatal(err)
}
return readTx
}
checkWithReadTxn := func(txn *bolt.Tx, key string, wantValue []byte) {
value := getWithTxn(txn, key)
if !bytes.Equal(value, wantValue) {
t.Errorf("Wanted value to be %s for key %s, but got %s", wantValue, key, string(value))
}
}
rollback := func(txn *bolt.Tx) {
if err := txn.Rollback(); err != nil {
t.Fatal(err)
}
}
put("k1", "v1")
rtx1 := openReadTxn()
put("k2", "v2")
hold1 := openReadTxn()
put("k3", "v3")
hold2 := openReadTxn()
del("k3")
rtx2 := openReadTxn()
del("k1")
hold3 := openReadTxn()
del("k2")
hold4 := openReadTxn()
put("k4", "v4")
hold5 := openReadTxn()
// Close the read transactions we established to hold a portion of the pages in pending state.
rollback(hold1)
rollback(hold2)
rollback(hold3)
rollback(hold4)
rollback(hold5)
// Execute a write transaction to trigger a releaseRange operation in the db
// that will free multiple ranges between the remaining open read transactions, now that the
// holds have been rolled back.
put("k4", "v4")
// Check that all long running reads still read correct values.
checkWithReadTxn(rtx1, "k1", []byte("v1"))
checkWithReadTxn(rtx2, "k2", []byte("v2"))
rollback(rtx1)
rollback(rtx2)
// Check that the final state is correct.
rtx7 := openReadTxn()
checkWithReadTxn(rtx7, "k1", nil)
checkWithReadTxn(rtx7, "k2", nil)
checkWithReadTxn(rtx7, "k3", nil)
checkWithReadTxn(rtx7, "k4", []byte("v4"))
rollback(rtx7)
}
func ExampleTx_Rollback() {
// Open the database.
db, err := bolt.Open(tempfile(), 0666, nil)