forked from TrueCloudLab/rclone
vendor: update all dependencies to latest versions
This commit is contained in:
parent
8e83fb6fb9
commit
7d3a17725d
4878 changed files with 1974229 additions and 201215 deletions
1
vendor/github.com/coreos/bbolt/.gitignore
generated
vendored
1
vendor/github.com/coreos/bbolt/.gitignore
generated
vendored
|
@ -2,3 +2,4 @@
|
|||
*.test
|
||||
*.swp
|
||||
/bin/
|
||||
cmd/bolt/bolt
|
||||
|
|
15
vendor/github.com/coreos/bbolt/Makefile
generated
vendored
15
vendor/github.com/coreos/bbolt/Makefile
generated
vendored
|
@ -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
|
||||
|
|
3
vendor/github.com/coreos/bbolt/README.md
generated
vendored
3
vendor/github.com/coreos/bbolt/README.md
generated
vendored
|
@ -5,7 +5,7 @@ bbolt
|
|||
[](https://codecov.io/gh/coreos/bbolt)
|
||||
[](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
30
vendor/github.com/coreos/bbolt/allocate_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
33
vendor/github.com/coreos/bbolt/bolt_unix.go
generated
vendored
33
vendor/github.com/coreos/bbolt/bolt_unix.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
39
vendor/github.com/coreos/bbolt/bolt_unix_solaris.go
generated
vendored
39
vendor/github.com/coreos/bbolt/bolt_unix_solaris.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
31
vendor/github.com/coreos/bbolt/bolt_windows.go
generated
vendored
31
vendor/github.com/coreos/bbolt/bolt_windows.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
186
vendor/github.com/coreos/bbolt/cmd/bolt/main.go
generated
vendored
186
vendor/github.com/coreos/bbolt/cmd/bolt/main.go
generated
vendored
|
@ -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
56
vendor/github.com/coreos/bbolt/db.go
generated
vendored
|
@ -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
|
||||
}
|
||||
|
|
154
vendor/github.com/coreos/bbolt/db_test.go
generated
vendored
154
vendor/github.com/coreos/bbolt/db_test.go
generated
vendored
|
@ -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.
|
||||
|
|
9
vendor/github.com/coreos/bbolt/freelist.go
generated
vendored
9
vendor/github.com/coreos/bbolt/freelist.go
generated
vendored
|
@ -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)
|
||||
|
|
128
vendor/github.com/coreos/bbolt/freelist_test.go
generated
vendored
128
vendor/github.com/coreos/bbolt/freelist_test.go
generated
vendored
|
@ -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
15
vendor/github.com/coreos/bbolt/tx.go
generated
vendored
|
@ -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
|
||||
|
|
153
vendor/github.com/coreos/bbolt/tx_test.go
generated
vendored
153
vendor/github.com/coreos/bbolt/tx_test.go
generated
vendored
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue