Skip to content
This repository has been archived by the owner on Mar 9, 2019. It is now read-only.

Commit

Permalink
Merge pull request #208 from benbjohnson/open-timeout
Browse files Browse the repository at this point in the history
Add Open() options, flock timeout.
  • Loading branch information
benbjohnson committed Jun 22, 2014
2 parents 622c1f1 + 00ee0da commit 4f428fe
Show file tree
Hide file tree
Showing 20 changed files with 172 additions and 132 deletions.
41 changes: 0 additions & 41 deletions bolt_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package bolt

import (
"os"
"syscall"
"unsafe"
)

var odirect int
Expand All @@ -12,42 +10,3 @@ var odirect int
func fdatasync(f *os.File) error {
return f.Sync()
}

// flock acquires an advisory lock on a file descriptor.
func flock(f *os.File) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
}

// funlock releases an advisory lock on a file descriptor.
func funlock(f *os.File) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
}

// mmap memory maps a DB's data file.
func mmap(db *DB, sz int) error {
b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return err
}

// Save the original byte slice and convert to a byte array pointer.
db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz
return nil
}

// munmap unmaps a DB's data file from memory.
func munmap(db *DB) error {
// Ignore the unmap if we have no mapped data.
if db.dataref == nil {
return nil
}

// Unmap using the original byte slice.
err := syscall.Munmap(db.dataref)
db.dataref = nil
db.data = nil
db.datasz = 0
return err
}
40 changes: 0 additions & 40 deletions bolt_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package bolt
import (
"os"
"syscall"
"unsafe"
)

var odirect = syscall.O_DIRECT
Expand All @@ -12,42 +11,3 @@ var odirect = syscall.O_DIRECT
func fdatasync(f *os.File) error {
return syscall.Fdatasync(int(f.Fd()))
}

// flock acquires an advisory lock on a file descriptor.
func flock(f *os.File) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_EX)
}

// funlock releases an advisory lock on a file descriptor.
func funlock(f *os.File) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
}

// mmap memory maps a DB's data file.
func mmap(db *DB, sz int) error {
b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return err
}

// Save the original byte slice and convert to a byte array pointer.
db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz
return nil
}

// munmap unmaps a DB's data file from memory.
func munmap(db *DB) error {
// Ignore the unmap if we have no mapped data.
if db.dataref == nil {
return nil
}

// Unmap using the original byte slice.
err := syscall.Munmap(db.dataref)
db.dataref = nil
db.data = nil
db.datasz = 0
return err
}
69 changes: 69 additions & 0 deletions bolt_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// +build linux darwin

package bolt

import (
"os"
"syscall"
"time"
"unsafe"
)

// flock acquires an advisory lock on a file descriptor.
func flock(f *os.File, timeout time.Duration) error {
var t time.Time
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
}

// Otherwise attempt to obtain an exclusive lock.
err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err == nil {
return nil
} else if err != syscall.EWOULDBLOCK {
return err
}

// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
}
}

// funlock releases an advisory lock on a file descriptor.
func funlock(f *os.File) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
}

// mmap memory maps a DB's data file.
func mmap(db *DB, sz int) error {
b, err := syscall.Mmap(int(db.file.Fd()), 0, sz, syscall.PROT_READ, syscall.MAP_SHARED)
if err != nil {
return err
}

// Save the original byte slice and convert to a byte array pointer.
db.dataref = b
db.data = (*[maxMapSize]byte)(unsafe.Pointer(&b[0]))
db.datasz = sz
return nil
}

// munmap unmaps a DB's data file from memory.
func munmap(db *DB) error {
// Ignore the unmap if we have no mapped data.
if db.dataref == nil {
return nil
}

// Unmap using the original byte slice.
err := syscall.Munmap(db.dataref)
db.dataref = nil
db.data = nil
db.datasz = 0
return err
}
3 changes: 2 additions & 1 deletion bolt_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"os"
"syscall"
"time"
"unsafe"
)

Expand All @@ -15,7 +16,7 @@ func fdatasync(f *os.File) error {
}

// flock acquires an advisory lock on a file descriptor.
func flock(f *os.File) error {
func flock(f *os.File, _ time.Duration) error {
return nil
}

Expand Down
6 changes: 3 additions & 3 deletions bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,7 @@ func TestBucket_Delete_Quick(t *testing.T) {

func ExampleBucket_Put() {
// Open the database.
db, _ := Open(tempfile(), 0666)
db, _ := Open(tempfile(), 0666, nil)
defer os.Remove(db.Path())
defer db.Close()

Expand All @@ -1030,7 +1030,7 @@ func ExampleBucket_Put() {

func ExampleBucket_Delete() {
// Open the database.
db, _ := Open(tempfile(), 0666)
db, _ := Open(tempfile(), 0666, nil)
defer os.Remove(db.Path())
defer db.Close()

Expand Down Expand Up @@ -1070,7 +1070,7 @@ func ExampleBucket_Delete() {

func ExampleBucket_ForEach() {
// Open the database.
db, _ := Open(tempfile(), 0666)
db, _ := Open(tempfile(), 0666, nil)
defer os.Remove(db.Path())
defer db.Close()

Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/bench.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Bench(options *BenchOptions) {
}

// Create database.
db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Buckets(path string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Check(path string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func Export(path string) {
}

// Open the database.
db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Get(path, name, key string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Import(path string, input string) {

func importBuckets(path string, root []*rawMessage) {
// Open the database.
db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestImport(t *testing.T) {
assert.Equal(t, ``, output)

// Open database and verify contents.
db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
assert.NoError(t, err)
db.View(func(tx *bolt.Tx) error {
assert.NotNil(t, tx.Bucket([]byte("empty")))
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Info(path string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Keys(path, name string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func open(fn func(*bolt.DB, string)) {
path := tempfile()
defer os.RemoveAll(path)

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
panic("db open error: " + err.Error())
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/pages.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func Pages(path string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/bolt/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func Stats(path, prefix string) {
return
}

db, err := bolt.Open(path, 0600)
db, err := bolt.Open(path, 0600, nil)
if err != nil {
fatal(err)
return
Expand Down
23 changes: 21 additions & 2 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"runtime/debug"
"strings"
"sync"
"time"
"unsafe"
)

Expand Down Expand Up @@ -50,6 +51,10 @@ var (

// ErrChecksum is returned when either meta page checksum does not match.
ErrChecksum = errors.New("checksum error")

// ErrTimeout is returned when a database cannot obtain an exclusive lock
// on the data file after the timeout passed to Open().
ErrTimeout = errors.New("timeout")
)

// DB represents a collection of buckets persisted to a file on disk.
Expand Down Expand Up @@ -108,9 +113,15 @@ func (db *DB) String() string {

// Open creates and opens a database at the given path.
// If the file does not exist then it will be created automatically.
func Open(path string, mode os.FileMode) (*DB, error) {
// 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, FillPercent: DefaultFillPercent}

// Set default options.
if options == nil {
options = &Options{}
}

// Open data file and separate sync handler for metadata writes.
db.path = path

Expand All @@ -123,7 +134,7 @@ func Open(path string, mode os.FileMode) (*DB, error) {
// Lock file so that other processes using Bolt cannot use the database
// at the same time. This would cause corruption since the two processes
// would write meta pages and free pages separately.
if err := flock(db.file); err != nil {
if err := flock(db.file, options.Timeout); err != nil {
_ = db.close()
return nil, err
}
Expand Down Expand Up @@ -556,6 +567,14 @@ func (db *DB) allocate(count int) (*page, error) {
return p, nil
}

// Options represents the options that can be set when opening a database.
type Options struct {
// Timeout is the amount of time to wait to obtain a file lock.
// When set to zero it will wait indefinitely. This option is only
// available on Darwin and Linux.
Timeout time.Duration
}

// Stats represents statistics about the database.
type Stats struct {
// Freelist stats
Expand Down
Loading

0 comments on commit 4f428fe

Please sign in to comment.