mirror of https://github.com/etcd-io/bbolt.git
use a shared lock in read-only mode
https://github.com/boltdb/bolt/pull/371#issuecomment-103119486pull/34/head
parent
019bf5b010
commit
fda75748b5
|
@ -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 {
|
||||||
|
|
|
@ -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
29
db.go
|
@ -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().
|
||||||
|
|
17
db_test.go
17
db_test.go
|
@ -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} {
|
||||||
|
|
Loading…
Reference in New Issue