mirror of https://github.com/etcd-io/bbolt.git
Merge remote-tracking branch 'boltdb/master' into project-list
# Conflicts: # README.mdpull/24/head
commit
8192480115
|
@ -437,7 +437,7 @@ all the keys in a bucket:
|
||||||
db.View(func(tx *bolt.Tx) error {
|
db.View(func(tx *bolt.Tx) error {
|
||||||
// Assume bucket exists and has keys
|
// Assume bucket exists and has keys
|
||||||
b := tx.Bucket([]byte("MyBucket"))
|
b := tx.Bucket([]byte("MyBucket"))
|
||||||
|
|
||||||
b.ForEach(func(k, v []byte) error {
|
b.ForEach(func(k, v []byte) error {
|
||||||
fmt.Printf("key=%s, value=%s\n", k, v)
|
fmt.Printf("key=%s, value=%s\n", k, v)
|
||||||
return nil
|
return nil
|
||||||
|
@ -617,7 +617,7 @@ Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
|
||||||
{
|
{
|
||||||
NSURL* URL= [NSURL fileURLWithPath: filePathString];
|
NSURL* URL= [NSURL fileURLWithPath: filePathString];
|
||||||
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
|
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
|
||||||
|
|
||||||
NSError *error = nil;
|
NSError *error = nil;
|
||||||
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
|
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
|
||||||
forKey: NSURLIsExcludedFromBackupKey error: &error];
|
forKey: NSURLIsExcludedFromBackupKey error: &error];
|
||||||
|
@ -839,6 +839,7 @@ Below is a list of public, open source projects that use Bolt:
|
||||||
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
|
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
|
||||||
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
|
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
|
||||||
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
|
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
|
||||||
* [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB
|
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
|
||||||
|
* [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB.
|
||||||
|
|
||||||
If you are using Bolt in a project please send a pull request to add it to the list.
|
If you are using Bolt in a project please send a pull request to add it to the list.
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// flock acquires an advisory lock on a file descriptor.
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
|
||||||
var t time.Time
|
var t time.Time
|
||||||
for {
|
for {
|
||||||
// If we're beyond our timeout then return an error.
|
// If we're beyond our timeout then return an error.
|
||||||
|
@ -27,7 +27,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise attempt to obtain an exclusive lock.
|
// Otherwise attempt to obtain an exclusive lock.
|
||||||
err := syscall.Flock(int(f.Fd()), flag|syscall.LOCK_NB)
|
err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if err != syscall.EWOULDBLOCK {
|
} else if err != syscall.EWOULDBLOCK {
|
||||||
|
@ -40,8 +40,8 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// funlock releases an advisory lock on a file descriptor.
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
func funlock(f *os.File) error {
|
func funlock(db *DB) error {
|
||||||
return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
|
return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mmap memory maps a DB's data file.
|
// mmap memory maps a DB's data file.
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// flock acquires an advisory lock on a file descriptor.
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
|
||||||
var t time.Time
|
var t time.Time
|
||||||
for {
|
for {
|
||||||
// If we're beyond our timeout then return an error.
|
// If we're beyond our timeout then return an error.
|
||||||
|
@ -32,7 +32,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
} else {
|
} else {
|
||||||
lock.Type = syscall.F_RDLCK
|
lock.Type = syscall.F_RDLCK
|
||||||
}
|
}
|
||||||
err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock)
|
err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if err != syscall.EAGAIN {
|
} else if err != syscall.EAGAIN {
|
||||||
|
@ -45,13 +45,13 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// funlock releases an advisory lock on a file descriptor.
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
func funlock(f *os.File) error {
|
func funlock(db *DB) error {
|
||||||
var lock syscall.Flock_t
|
var lock syscall.Flock_t
|
||||||
lock.Start = 0
|
lock.Start = 0
|
||||||
lock.Len = 0
|
lock.Len = 0
|
||||||
lock.Type = syscall.F_UNLCK
|
lock.Type = syscall.F_UNLCK
|
||||||
lock.Whence = 0
|
lock.Whence = 0
|
||||||
return syscall.FcntlFlock(uintptr(f.Fd()), syscall.F_SETLK, &lock)
|
return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mmap memory maps a DB's data file.
|
// mmap memory maps a DB's data file.
|
||||||
|
|
|
@ -16,6 +16,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
lockExt = ".lock"
|
||||||
|
|
||||||
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
|
||||||
flagLockExclusive = 2
|
flagLockExclusive = 2
|
||||||
flagLockFailImmediately = 1
|
flagLockFailImmediately = 1
|
||||||
|
@ -46,7 +48,16 @@ func fdatasync(db *DB) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// flock acquires an advisory lock on a file descriptor.
|
// flock acquires an advisory lock on a file descriptor.
|
||||||
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
|
||||||
|
// Create a separate lock file on windows because a process
|
||||||
|
// cannot share an exclusive lock on the same file. This is
|
||||||
|
// needed during Tx.WriteTo().
|
||||||
|
f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
db.lockfile = f
|
||||||
|
|
||||||
var t time.Time
|
var t time.Time
|
||||||
for {
|
for {
|
||||||
// If we're beyond our timeout then return an error.
|
// If we're beyond our timeout then return an error.
|
||||||
|
@ -62,7 +73,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
flag |= flagLockExclusive
|
flag |= flagLockExclusive
|
||||||
}
|
}
|
||||||
|
|
||||||
err := lockFileEx(syscall.Handle(f.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
|
err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
} else if err != errLockViolation {
|
} else if err != errLockViolation {
|
||||||
|
@ -75,8 +86,11 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// funlock releases an advisory lock on a file descriptor.
|
// funlock releases an advisory lock on a file descriptor.
|
||||||
func funlock(f *os.File) error {
|
func funlock(db *DB) error {
|
||||||
return unlockFileEx(syscall.Handle(f.Fd()), 0, 1, 0, &syscall.Overlapped{})
|
err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
|
||||||
|
db.lockfile.Close()
|
||||||
|
os.Remove(db.path+lockExt)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// mmap memory maps a DB's data file.
|
// mmap memory maps a DB's data file.
|
||||||
|
|
15
db.go
15
db.go
|
@ -93,6 +93,7 @@ type DB struct {
|
||||||
|
|
||||||
path string
|
path string
|
||||||
file *os.File
|
file *os.File
|
||||||
|
lockfile *os.File // windows only
|
||||||
dataref []byte // mmap'ed readonly, write throws SEGV
|
dataref []byte // mmap'ed readonly, write throws SEGV
|
||||||
data *[maxMapSize]byte
|
data *[maxMapSize]byte
|
||||||
datasz int
|
datasz int
|
||||||
|
@ -177,7 +178,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
||||||
// if !options.ReadOnly.
|
// if !options.ReadOnly.
|
||||||
// The database file is locked using the shared lock (more than one process may
|
// The database file is locked using the shared lock (more than one process may
|
||||||
// hold a lock at the same time) otherwise (options.ReadOnly is set).
|
// hold a lock at the same time) otherwise (options.ReadOnly is set).
|
||||||
if err := flock(db.file, !db.readOnly, options.Timeout); err != nil {
|
if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil {
|
||||||
_ = db.close()
|
_ = db.close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -379,6 +380,10 @@ func (db *DB) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) close() error {
|
func (db *DB) close() error {
|
||||||
|
if !db.opened {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
db.opened = false
|
db.opened = false
|
||||||
|
|
||||||
db.freelist = nil
|
db.freelist = nil
|
||||||
|
@ -397,7 +402,7 @@ func (db *DB) close() error {
|
||||||
// No need to unlock read-only file.
|
// No need to unlock read-only file.
|
||||||
if !db.readOnly {
|
if !db.readOnly {
|
||||||
// Unlock the file.
|
// Unlock the file.
|
||||||
if err := funlock(db.file); err != nil {
|
if err := funlock(db); err != nil {
|
||||||
log.Printf("bolt.Close(): funlock error: %s", err)
|
log.Printf("bolt.Close(): funlock error: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -824,8 +829,10 @@ func (db *DB) grow(sz int) error {
|
||||||
// Truncate and fsync to ensure file size metadata is flushed.
|
// Truncate and fsync to ensure file size metadata is flushed.
|
||||||
// https://github.com/boltdb/bolt/issues/284
|
// https://github.com/boltdb/bolt/issues/284
|
||||||
if !db.NoGrowSync && !db.readOnly {
|
if !db.NoGrowSync && !db.readOnly {
|
||||||
if err := db.file.Truncate(int64(sz)); err != nil {
|
if runtime.GOOS != "windows" {
|
||||||
return fmt.Errorf("file resize error: %s", err)
|
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||||
|
return fmt.Errorf("file resize error: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err := db.file.Sync(); err != nil {
|
if err := db.file.Sync(); err != nil {
|
||||||
return fmt.Errorf("file sync error: %s", err)
|
return fmt.Errorf("file sync error: %s", err)
|
||||||
|
|
37
node.go
37
node.go
|
@ -463,43 +463,6 @@ func (n *node) rebalance() {
|
||||||
target = n.prevSibling()
|
target = n.prevSibling()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If target node has extra nodes then just move one over.
|
|
||||||
if target.numChildren() > target.minKeys() {
|
|
||||||
if useNextSibling {
|
|
||||||
// Reparent and move node.
|
|
||||||
if child, ok := n.bucket.nodes[target.inodes[0].pgid]; ok {
|
|
||||||
child.parent.removeChild(child)
|
|
||||||
child.parent = n
|
|
||||||
child.parent.children = append(child.parent.children, child)
|
|
||||||
}
|
|
||||||
n.inodes = append(n.inodes, target.inodes[0])
|
|
||||||
target.inodes = target.inodes[1:]
|
|
||||||
|
|
||||||
// Update target key on parent.
|
|
||||||
target.parent.put(target.key, target.inodes[0].key, nil, target.pgid, 0)
|
|
||||||
target.key = target.inodes[0].key
|
|
||||||
_assert(len(target.key) > 0, "rebalance(1): zero-length node key")
|
|
||||||
} else {
|
|
||||||
// Reparent and move node.
|
|
||||||
if child, ok := n.bucket.nodes[target.inodes[len(target.inodes)-1].pgid]; ok {
|
|
||||||
child.parent.removeChild(child)
|
|
||||||
child.parent = n
|
|
||||||
child.parent.children = append(child.parent.children, child)
|
|
||||||
}
|
|
||||||
n.inodes = append(n.inodes, inode{})
|
|
||||||
copy(n.inodes[1:], n.inodes)
|
|
||||||
n.inodes[0] = target.inodes[len(target.inodes)-1]
|
|
||||||
target.inodes = target.inodes[:len(target.inodes)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update parent key for node.
|
|
||||||
n.parent.put(n.key, n.inodes[0].key, nil, n.pgid, 0)
|
|
||||||
n.key = n.inodes[0].key
|
|
||||||
_assert(len(n.key) > 0, "rebalance(2): zero-length node key")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both this node and the target node are too small then merge them.
|
// If both this node and the target node are too small then merge them.
|
||||||
if useNextSibling {
|
if useNextSibling {
|
||||||
// Reparent all child nodes being moved.
|
// Reparent all child nodes being moved.
|
||||||
|
|
46
tx.go
46
tx.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
@ -202,8 +203,17 @@ func (tx *Tx) Commit() error {
|
||||||
// If strict mode is enabled then perform a consistency check.
|
// If strict mode is enabled then perform a consistency check.
|
||||||
// Only the first consistency error is reported in the panic.
|
// Only the first consistency error is reported in the panic.
|
||||||
if tx.db.StrictMode {
|
if tx.db.StrictMode {
|
||||||
if err, ok := <-tx.Check(); ok {
|
ch := tx.Check()
|
||||||
panic("check fail: " + err.Error())
|
var errs []string
|
||||||
|
for {
|
||||||
|
err, ok := <-ch
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
errs = append(errs, err.Error())
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
panic("check fail: " + strings.Join(errs, "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,12 +307,34 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
}
|
}
|
||||||
defer func() { _ = f.Close() }()
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
// Copy the meta pages.
|
// Generate a meta page. We use the same page data for both meta pages.
|
||||||
tx.db.metalock.Lock()
|
buf := make([]byte, tx.db.pageSize)
|
||||||
n, err = io.CopyN(w, f, int64(tx.db.pageSize*2))
|
page := (*page)(unsafe.Pointer(&buf[0]))
|
||||||
tx.db.metalock.Unlock()
|
page.flags = metaPageFlag
|
||||||
|
*page.meta() = *tx.meta
|
||||||
|
|
||||||
|
// Write meta 0.
|
||||||
|
page.id = 0
|
||||||
|
page.meta().checksum = page.meta().sum64()
|
||||||
|
nn, err := w.Write(buf)
|
||||||
|
n += int64(nn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return n, fmt.Errorf("meta copy: %s", err)
|
return n, fmt.Errorf("meta 0 copy: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write meta 1 with a lower transaction id.
|
||||||
|
page.id = 1
|
||||||
|
page.meta().txid -= 1
|
||||||
|
page.meta().checksum = page.meta().sum64()
|
||||||
|
nn, err = w.Write(buf)
|
||||||
|
n += int64(nn)
|
||||||
|
if err != nil {
|
||||||
|
return n, fmt.Errorf("meta 1 copy: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move past the meta pages in the file.
|
||||||
|
if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil {
|
||||||
|
return n, fmt.Errorf("seek: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy data pages.
|
// Copy data pages.
|
||||||
|
|
|
@ -570,7 +570,7 @@ func TestTx_CopyFile_Error_Meta(t *testing.T) {
|
||||||
|
|
||||||
if err := db.View(func(tx *bolt.Tx) error {
|
if err := db.View(func(tx *bolt.Tx) error {
|
||||||
return tx.Copy(&failWriter{})
|
return tx.Copy(&failWriter{})
|
||||||
}); err == nil || err.Error() != "meta copy: error injected for tests" {
|
}); err == nil || err.Error() != "meta 0 copy: error injected for tests" {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue