Skip to content

Commit

Permalink
use a shared lock in read-only mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sasha-s committed May 18, 2015
1 parent 019bf5b commit fda7574
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 25 deletions.
8 changes: 6 additions & 2 deletions bolt_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// flock acquires an advisory lock on a file descriptor.
func flock(f *os.File, timeout time.Duration) error {
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
var t time.Time
for {
// If we're beyond our timeout then return an error.
Expand All @@ -21,9 +21,13 @@ func flock(f *os.File, timeout time.Duration) error {
} 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(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
err := syscall.Flock(int(f.Fd()), flag|syscall.LOCK_NB)
if err == nil {
return nil
} else if err != syscall.EWOULDBLOCK {
Expand Down
2 changes: 1 addition & 1 deletion bolt_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func fdatasync(db *DB) error {
}

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

Expand Down
33 changes: 18 additions & 15 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,8 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
db.MaxBatchSize = DefaultMaxBatchSize
db.MaxBatchDelay = DefaultMaxBatchDelay

// Get file stats.
s, err := os.Stat(path)
flag := os.O_RDWR
if err != nil && !os.IsNotExist(err) {
return nil, err
} else if err == nil && (s.Mode().Perm()&0222) == 0 {
// remove www from mode as well.
mode ^= (mode & 0222)
if options.ReadOnly {
flag = os.O_RDONLY
db.readOnly = true
// Ignore truncations.
Expand All @@ -156,21 +150,26 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {

// Open data file and separate sync handler for metadata writes.
db.path = path
var err error
if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil {
_ = db.close()
return nil, err
}

// No need to lock read-only file.
if !db.readOnly {
db.ops.Truncate = db.file.Truncate
// 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, options.Timeout); err != nil {
_ = db.close()
return nil, err
}
}

// Lock file so that other processes using Bolt in read-write mode cannot
// use the database at the same time. This would cause corruption since
// the two processes would write meta pages and free pages separately.
// The database file is locked exclusively (only one process can grab the lock)
// if !options.ReadOnly.
// 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).
if err := flock(db.file, !db.readOnly, options.Timeout); err != nil {
_ = db.close()
return nil, err
}

// Default values for test hooks
Expand Down Expand Up @@ -660,6 +659,10 @@ type Options struct {

// Sets the DB.NoGrowSync flag before memory mapping the file.
NoGrowSync bool

// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to
// grab a shared lock (UNIX).
ReadOnly bool
}

// DefaultOptions represent the options used if nil options are passed into Open().
Expand Down
17 changes: 10 additions & 7 deletions db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ func TestDB_Open_FileTooSmall(t *testing.T) {
equals(t, errors.New("file size too small"), err)
}

// Ensure that a database can be opened in read-only mode.
// Ensure that a database can be opened in read-only mode by multiple processes
// and that a database can not be opened in read-write mode and in read-only
// mode at the same time.
func TestOpen_ReadOnly(t *testing.T) {
var bucket = []byte(`bucket`)
var key = []byte(`key`)
Expand All @@ -243,14 +245,15 @@ func TestOpen_ReadOnly(t *testing.T) {
assert(t, !db.IsReadOnly(), "")
ok(t, err)
ok(t, db.Close())
// Make it read-only.
ok(t, os.Chmod(path, 0444))
// Open again.
db0, err := bolt.Open(path, 0666, nil)
// Open in read-only mode.
db0, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
ok(t, err)
defer db0.Close()
// And again.
db1, err := bolt.Open(path, 0666, nil)
// Try opening in regular mode.
_, err = bolt.Open(path, 0666, &bolt.Options{Timeout: time.Millisecond * 100})
assert(t, err != nil, "")
// And again (in read-only mode).
db1, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
ok(t, err)
defer db1.Close()
for _, db := range []*bolt.DB{db0, db1} {
Expand Down

0 comments on commit fda7574

Please sign in to comment.