sasha-s 2015-05-18 11:07:12 -07:00
parent 019bf5b010
commit fda75748b5
4 changed files with 35 additions and 25 deletions

View 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, timeout time.Duration) error { func flock(f *os.File, 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.
@ -21,9 +21,13 @@ func flock(f *os.File, timeout time.Duration) error {
} else if timeout > 0 && time.Since(t) > timeout { } else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout return ErrTimeout
} }
flag := syscall.LOCK_SH
if exclusive {
flag = syscall.LOCK_EX
}
// Otherwise attempt to obtain an exclusive lock. // 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 { if err == nil {
return nil return nil
} else if err != syscall.EWOULDBLOCK { } else if err != syscall.EWOULDBLOCK {

View File

@ -16,7 +16,7 @@ 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, _ time.Duration) error { func flock(f *os.File, _ bool, _ time.Duration) error {
return nil return nil
} }

29
db.go
View File

@ -140,14 +140,8 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
db.MaxBatchSize = DefaultMaxBatchSize db.MaxBatchSize = DefaultMaxBatchSize
db.MaxBatchDelay = DefaultMaxBatchDelay db.MaxBatchDelay = DefaultMaxBatchDelay
// Get file stats.
s, err := os.Stat(path)
flag := os.O_RDWR flag := os.O_RDWR
if err != nil && !os.IsNotExist(err) { if options.ReadOnly {
return nil, err
} else if err == nil && (s.Mode().Perm()&0222) == 0 {
// remove www from mode as well.
mode ^= (mode & 0222)
flag = os.O_RDONLY flag = os.O_RDONLY
db.readOnly = true db.readOnly = true
// Ignore truncations. // Ignore truncations.
@ -156,22 +150,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// Open data file and separate sync handler for metadata writes. // Open data file and separate sync handler for metadata writes.
db.path = path db.path = path
var err error
if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil { if db.file, err = os.OpenFile(db.path, flag|os.O_CREATE, mode); err != nil {
_ = db.close() _ = db.close()
return nil, err return nil, err
} }
// No need to lock read-only file.
if !db.readOnly { if !db.readOnly {
db.ops.Truncate = db.file.Truncate 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. // Lock file so that other processes using Bolt in read-write mode cannot
if err := flock(db.file, options.Timeout); err != nil { // 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() _ = db.close()
return nil, err return nil, err
} }
}
// Default values for test hooks // Default values for test hooks
db.ops.writeAt = db.file.WriteAt db.ops.writeAt = db.file.WriteAt
@ -660,6 +659,10 @@ type Options struct {
// Sets the DB.NoGrowSync flag before memory mapping the file. // Sets the DB.NoGrowSync flag before memory mapping the file.
NoGrowSync bool 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(). // DefaultOptions represent the options used if nil options are passed into Open().

View File

@ -224,7 +224,9 @@ func TestDB_Open_FileTooSmall(t *testing.T) {
equals(t, errors.New("file size too small"), err) 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) { func TestOpen_ReadOnly(t *testing.T) {
var bucket = []byte(`bucket`) var bucket = []byte(`bucket`)
var key = []byte(`key`) var key = []byte(`key`)
@ -243,14 +245,15 @@ func TestOpen_ReadOnly(t *testing.T) {
assert(t, !db.IsReadOnly(), "") assert(t, !db.IsReadOnly(), "")
ok(t, err) ok(t, err)
ok(t, db.Close()) ok(t, db.Close())
// Make it read-only. // Open in read-only mode.
ok(t, os.Chmod(path, 0444)) db0, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
// Open again.
db0, err := bolt.Open(path, 0666, nil)
ok(t, err) ok(t, err)
defer db0.Close() defer db0.Close()
// And again. // Try opening in regular mode.
db1, err := bolt.Open(path, 0666, nil) _, 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) ok(t, err)
defer db1.Close() defer db1.Close()
for _, db := range []*bolt.DB{db0, db1} { for _, db := range []*bolt.DB{db0, db1} {