update vendor
This commit is contained in:
parent
824f46979b
commit
8675bc4b7a
328 changed files with 27166 additions and 6537 deletions
467
vendor/go.etcd.io/bbolt/db.go
generated
vendored
467
vendor/go.etcd.io/bbolt/db.go
generated
vendored
|
|
@ -3,49 +3,28 @@ package bbolt
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
berrors "go.etcd.io/bbolt/errors"
|
||||
"go.etcd.io/bbolt/internal/common"
|
||||
fl "go.etcd.io/bbolt/internal/freelist"
|
||||
)
|
||||
|
||||
// The largest step that can be taken when remapping the mmap.
|
||||
const maxMmapStep = 1 << 30 // 1GB
|
||||
|
||||
// The data file format version.
|
||||
const version = 2
|
||||
|
||||
// Represents a marker value to indicate that a file is a Bolt DB.
|
||||
const magic uint32 = 0xED0CDAED
|
||||
|
||||
const pgidNoFreelist pgid = 0xffffffffffffffff
|
||||
|
||||
// IgnoreNoSync specifies whether the NoSync field of a DB is ignored when
|
||||
// syncing changes to a file. This is required as some operating systems,
|
||||
// such as OpenBSD, do not have a unified buffer cache (UBC) and writes
|
||||
// must be synchronized using the msync(2) syscall.
|
||||
const IgnoreNoSync = runtime.GOOS == "openbsd"
|
||||
|
||||
// Default values if not set in a DB instance.
|
||||
const (
|
||||
DefaultMaxBatchSize int = 1000
|
||||
DefaultMaxBatchDelay = 10 * time.Millisecond
|
||||
DefaultAllocSize = 16 * 1024 * 1024
|
||||
)
|
||||
|
||||
// default page size for db is set to the OS page size.
|
||||
var defaultPageSize = os.Getpagesize()
|
||||
|
||||
// The time elapsed between consecutive file locking attempts.
|
||||
const flockRetryTimeout = 50 * time.Millisecond
|
||||
|
||||
// FreelistType is the type of the freelist backend
|
||||
type FreelistType string
|
||||
|
||||
// TODO(ahrtr): eventually we should (step by step)
|
||||
// 1. default to `FreelistMapType`;
|
||||
// 2. remove the `FreelistArrayType`, do not export `FreelistMapType`
|
||||
// and remove field `FreelistType' from both `DB` and `Options`;
|
||||
const (
|
||||
// FreelistArrayType indicates backend freelist type is array
|
||||
FreelistArrayType = FreelistType("array")
|
||||
|
|
@ -137,6 +116,8 @@ type DB struct {
|
|||
// Supported only on Unix via mlock/munlock syscalls.
|
||||
Mlock bool
|
||||
|
||||
logger Logger
|
||||
|
||||
path string
|
||||
openFile func(string, int, os.FileMode) (*os.File, error)
|
||||
file *os.File
|
||||
|
|
@ -144,17 +125,16 @@ type DB struct {
|
|||
// always fails on Windows platform.
|
||||
//nolint
|
||||
dataref []byte // mmap'ed readonly, write throws SEGV
|
||||
data *[maxMapSize]byte
|
||||
data *[common.MaxMapSize]byte
|
||||
datasz int
|
||||
filesz int // current on disk file size
|
||||
meta0 *meta
|
||||
meta1 *meta
|
||||
meta0 *common.Meta
|
||||
meta1 *common.Meta
|
||||
pageSize int
|
||||
opened bool
|
||||
rwtx *Tx
|
||||
txs []*Tx
|
||||
|
||||
freelist *freelist
|
||||
freelist fl.Interface
|
||||
freelistLoad sync.Once
|
||||
|
||||
pagePool sync.Pool
|
||||
|
|
@ -191,13 +171,15 @@ func (db *DB) String() string {
|
|||
return fmt.Sprintf("DB<%q>", db.path)
|
||||
}
|
||||
|
||||
// Open creates and opens a database at the given path.
|
||||
// If the file does not exist then it will be created automatically.
|
||||
// Open creates and opens a database at the given path with a given file mode.
|
||||
// If the file does not exist then it will be created automatically with a given file mode.
|
||||
// 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) {
|
||||
db := &DB{
|
||||
// Note: For read/write transactions, ensure the owner has write permission on the created/opened database file, e.g. 0600
|
||||
func Open(path string, mode os.FileMode, options *Options) (db *DB, err error) {
|
||||
db = &DB{
|
||||
opened: true,
|
||||
}
|
||||
|
||||
// Set default options if no options are provided.
|
||||
if options == nil {
|
||||
options = DefaultOptions
|
||||
|
|
@ -211,9 +193,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
db.Mlock = options.Mlock
|
||||
|
||||
// Set default values for later DB operations.
|
||||
db.MaxBatchSize = DefaultMaxBatchSize
|
||||
db.MaxBatchDelay = DefaultMaxBatchDelay
|
||||
db.AllocSize = DefaultAllocSize
|
||||
db.MaxBatchSize = common.DefaultMaxBatchSize
|
||||
db.MaxBatchDelay = common.DefaultMaxBatchDelay
|
||||
db.AllocSize = common.DefaultAllocSize
|
||||
|
||||
if options.Logger == nil {
|
||||
db.logger = getDiscardLogger()
|
||||
} else {
|
||||
db.logger = options.Logger
|
||||
}
|
||||
|
||||
lg := db.Logger()
|
||||
if lg != discardLogger {
|
||||
lg.Infof("Opening db file (%s) with mode %s and with options: %s", path, mode, options)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Opening bbolt db (%s) failed: %v", path, err)
|
||||
} else {
|
||||
lg.Infof("Opening bbolt db (%s) successfully", path)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
flag := os.O_RDWR
|
||||
if options.ReadOnly {
|
||||
|
|
@ -222,6 +222,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
} else {
|
||||
// always load free pages in write mode
|
||||
db.PreLoadFreelist = true
|
||||
flag |= os.O_CREATE
|
||||
}
|
||||
|
||||
db.openFile = options.OpenFile
|
||||
|
|
@ -230,9 +231,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
}
|
||||
|
||||
// Open data file and separate sync handler for metadata writes.
|
||||
var err error
|
||||
if db.file, err = db.openFile(path, flag|os.O_CREATE, mode); err != nil {
|
||||
if db.file, err = db.openFile(path, flag, mode); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to open db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
db.path = db.file.Name()
|
||||
|
|
@ -244,8 +245,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
// 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, !db.readOnly, options.Timeout); err != nil {
|
||||
if err = flock(db, !db.readOnly, options.Timeout); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to lock db file (%s), readonly: %t, error: %v", path, db.readOnly, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -254,27 +256,28 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
|
||||
if db.pageSize = options.PageSize; db.pageSize == 0 {
|
||||
// Set the default page size to the OS page size.
|
||||
db.pageSize = defaultPageSize
|
||||
db.pageSize = common.DefaultPageSize
|
||||
}
|
||||
|
||||
// Initialize the database if it doesn't exist.
|
||||
if info, err := db.file.Stat(); err != nil {
|
||||
if info, statErr := db.file.Stat(); statErr != nil {
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
lg.Errorf("failed to get db file's stats (%s): %v", path, err)
|
||||
return nil, statErr
|
||||
} else if info.Size() == 0 {
|
||||
// Initialize new files with meta pages.
|
||||
if err := db.init(); err != nil {
|
||||
if err = db.init(); err != nil {
|
||||
// clean up file descriptor on initialization fail
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to initialize db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// try to get the page size from the metadata pages
|
||||
if pgSize, err := db.getPageSize(); err == nil {
|
||||
db.pageSize = pgSize
|
||||
} else {
|
||||
if db.pageSize, err = db.getPageSize(); err != nil {
|
||||
_ = db.close()
|
||||
return nil, ErrInvalid
|
||||
lg.Errorf("failed to get page size from db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -286,8 +289,9 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
}
|
||||
|
||||
// Memory map the data file.
|
||||
if err := db.mmap(options.InitialMmapSize); err != nil {
|
||||
if err = db.mmap(options.InitialMmapSize); err != nil {
|
||||
_ = db.close()
|
||||
lg.Errorf("failed to map db file (%s): %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -302,13 +306,14 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
|
|||
// Flush freelist when transitioning from no sync to sync so
|
||||
// NoFreelistSync unaware boltdb can open the db later.
|
||||
if !db.NoFreelistSync && !db.hasSyncedFreelist() {
|
||||
tx, err := db.Begin(true)
|
||||
tx, txErr := db.Begin(true)
|
||||
if tx != nil {
|
||||
err = tx.Commit()
|
||||
txErr = tx.Commit()
|
||||
}
|
||||
if err != nil {
|
||||
if txErr != nil {
|
||||
lg.Errorf("starting readwrite transaction failed: %v", txErr)
|
||||
_ = db.close()
|
||||
return nil, err
|
||||
return nil, txErr
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,7 +357,7 @@ func (db *DB) getPageSize() (int, error) {
|
|||
return db.pageSize, nil
|
||||
}
|
||||
|
||||
return 0, ErrInvalid
|
||||
return 0, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// getPageSizeFromFirstMeta reads the pageSize from the first meta page
|
||||
|
|
@ -361,11 +366,11 @@ func (db *DB) getPageSizeFromFirstMeta() (int, bool, error) {
|
|||
var metaCanRead bool
|
||||
if bw, err := db.file.ReadAt(buf[:], 0); err == nil && bw == len(buf) {
|
||||
metaCanRead = true
|
||||
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
|
||||
return int(m.pageSize), metaCanRead, nil
|
||||
if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
|
||||
return int(m.PageSize()), metaCanRead, nil
|
||||
}
|
||||
}
|
||||
return 0, metaCanRead, ErrInvalid
|
||||
return 0, metaCanRead, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// getPageSizeFromSecondMeta reads the pageSize from the second meta page
|
||||
|
|
@ -397,13 +402,13 @@ func (db *DB) getPageSizeFromSecondMeta() (int, bool, error) {
|
|||
bw, err := db.file.ReadAt(buf[:], pos)
|
||||
if (err == nil && bw == len(buf)) || (err == io.EOF && int64(bw) == (fileSize-pos)) {
|
||||
metaCanRead = true
|
||||
if m := db.pageInBuffer(buf[:], 0).meta(); m.validate() == nil {
|
||||
return int(m.pageSize), metaCanRead, nil
|
||||
if m := db.pageInBuffer(buf[:], 0).Meta(); m.Validate() == nil {
|
||||
return int(m.PageSize()), metaCanRead, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, metaCanRead, ErrInvalid
|
||||
return 0, metaCanRead, berrors.ErrInvalid
|
||||
}
|
||||
|
||||
// loadFreelist reads the freelist if it is synced, or reconstructs it
|
||||
|
|
@ -414,17 +419,29 @@ func (db *DB) loadFreelist() {
|
|||
db.freelist = newFreelist(db.FreelistType)
|
||||
if !db.hasSyncedFreelist() {
|
||||
// Reconstruct free list by scanning the DB.
|
||||
db.freelist.readIDs(db.freepages())
|
||||
db.freelist.Init(db.freepages())
|
||||
} else {
|
||||
// Read free list from freelist page.
|
||||
db.freelist.read(db.page(db.meta().freelist))
|
||||
db.freelist.Read(db.page(db.meta().Freelist()))
|
||||
}
|
||||
db.stats.FreePageN = db.freelist.free_count()
|
||||
db.stats.FreePageN = db.freelist.FreeCount()
|
||||
})
|
||||
}
|
||||
|
||||
func (db *DB) hasSyncedFreelist() bool {
|
||||
return db.meta().freelist != pgidNoFreelist
|
||||
return db.meta().Freelist() != common.PgidNoFreelist
|
||||
}
|
||||
|
||||
func (db *DB) fileSize() (int, error) {
|
||||
info, err := db.file.Stat()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("file stat error: %w", err)
|
||||
}
|
||||
sz := int(info.Size())
|
||||
if sz < db.pageSize*2 {
|
||||
return 0, fmt.Errorf("file size too small %d", sz)
|
||||
}
|
||||
return sz, nil
|
||||
}
|
||||
|
||||
// mmap opens the underlying memory-mapped file and initializes the meta references.
|
||||
|
|
@ -433,21 +450,22 @@ func (db *DB) mmap(minsz int) (err error) {
|
|||
db.mmaplock.Lock()
|
||||
defer db.mmaplock.Unlock()
|
||||
|
||||
info, err := db.file.Stat()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mmap stat error: %s", err)
|
||||
} else if int(info.Size()) < db.pageSize*2 {
|
||||
return fmt.Errorf("file size too small")
|
||||
}
|
||||
lg := db.Logger()
|
||||
|
||||
// Ensure the size is at least the minimum size.
|
||||
fileSize := int(info.Size())
|
||||
var fileSize int
|
||||
fileSize, err = db.fileSize()
|
||||
if err != nil {
|
||||
lg.Errorf("getting file size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
var size = fileSize
|
||||
if size < minsz {
|
||||
size = minsz
|
||||
}
|
||||
size, err = db.mmapSize(size)
|
||||
if err != nil {
|
||||
lg.Errorf("getting map size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -472,6 +490,7 @@ func (db *DB) mmap(minsz int) (err error) {
|
|||
// gofail: var mapError string
|
||||
// return errors.New(mapError)
|
||||
if err = mmap(db, size); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] mmap failed, size: %d, error: %v", runtime.GOOS, runtime.GOARCH, size, err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -493,15 +512,16 @@ func (db *DB) mmap(minsz int) (err error) {
|
|||
}
|
||||
|
||||
// Save references to the meta pages.
|
||||
db.meta0 = db.page(0).meta()
|
||||
db.meta1 = db.page(1).meta()
|
||||
db.meta0 = db.page(0).Meta()
|
||||
db.meta1 = db.page(1).Meta()
|
||||
|
||||
// Validate the meta pages. We only return an error if both meta pages fail
|
||||
// validation, since meta0 failing validation means that it wasn't saved
|
||||
// properly -- but we can recover using meta1. And vice-versa.
|
||||
err0 := db.meta0.validate()
|
||||
err1 := db.meta1.validate()
|
||||
err0 := db.meta0.Validate()
|
||||
err1 := db.meta1.Validate()
|
||||
if err0 != nil && err1 != nil {
|
||||
lg.Errorf("both meta pages are invalid, meta0: %v, meta1: %v", err0, err1)
|
||||
return err0
|
||||
}
|
||||
|
||||
|
|
@ -524,6 +544,7 @@ func (db *DB) munmap() error {
|
|||
// gofail: var unmapError string
|
||||
// return errors.New(unmapError)
|
||||
if err := munmap(db); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munmap failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
|
||||
return fmt.Errorf("unmap error: %v", err.Error())
|
||||
}
|
||||
|
||||
|
|
@ -542,14 +563,14 @@ func (db *DB) mmapSize(size int) (int, error) {
|
|||
}
|
||||
|
||||
// Verify the requested size is not above the maximum allowed.
|
||||
if size > maxMapSize {
|
||||
return 0, fmt.Errorf("mmap too large")
|
||||
if size > common.MaxMapSize {
|
||||
return 0, errors.New("mmap too large")
|
||||
}
|
||||
|
||||
// If larger than 1GB then grow by 1GB at a time.
|
||||
sz := int64(size)
|
||||
if remainder := sz % int64(maxMmapStep); remainder > 0 {
|
||||
sz += int64(maxMmapStep) - remainder
|
||||
if remainder := sz % int64(common.MaxMmapStep); remainder > 0 {
|
||||
sz += int64(common.MaxMmapStep) - remainder
|
||||
}
|
||||
|
||||
// Ensure that the mmap size is a multiple of the page size.
|
||||
|
|
@ -560,8 +581,8 @@ func (db *DB) mmapSize(size int) (int, error) {
|
|||
}
|
||||
|
||||
// If we've exceeded the max size then only grow up to the max size.
|
||||
if sz > maxMapSize {
|
||||
sz = maxMapSize
|
||||
if sz > common.MaxMapSize {
|
||||
sz = common.MaxMapSize
|
||||
}
|
||||
|
||||
return int(sz), nil
|
||||
|
|
@ -571,6 +592,7 @@ func (db *DB) munlock(fileSize int) error {
|
|||
// gofail: var munlockError string
|
||||
// return errors.New(munlockError)
|
||||
if err := munlock(db, fileSize); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] munlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
|
||||
return fmt.Errorf("munlock error: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
|
|
@ -580,6 +602,7 @@ func (db *DB) mlock(fileSize int) error {
|
|||
// gofail: var mlockError string
|
||||
// return errors.New(mlockError)
|
||||
if err := mlock(db, fileSize); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] mlock failed, fileSize: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, fileSize, db.datasz, err)
|
||||
return fmt.Errorf("mlock error: %v", err.Error())
|
||||
}
|
||||
return nil
|
||||
|
|
@ -600,42 +623,43 @@ func (db *DB) init() error {
|
|||
// Create two meta pages on a buffer.
|
||||
buf := make([]byte, db.pageSize*4)
|
||||
for i := 0; i < 2; i++ {
|
||||
p := db.pageInBuffer(buf, pgid(i))
|
||||
p.id = pgid(i)
|
||||
p.flags = metaPageFlag
|
||||
p := db.pageInBuffer(buf, common.Pgid(i))
|
||||
p.SetId(common.Pgid(i))
|
||||
p.SetFlags(common.MetaPageFlag)
|
||||
|
||||
// Initialize the meta page.
|
||||
m := p.meta()
|
||||
m.magic = magic
|
||||
m.version = version
|
||||
m.pageSize = uint32(db.pageSize)
|
||||
m.freelist = 2
|
||||
m.root = bucket{root: 3}
|
||||
m.pgid = 4
|
||||
m.txid = txid(i)
|
||||
m.checksum = m.sum64()
|
||||
m := p.Meta()
|
||||
m.SetMagic(common.Magic)
|
||||
m.SetVersion(common.Version)
|
||||
m.SetPageSize(uint32(db.pageSize))
|
||||
m.SetFreelist(2)
|
||||
m.SetRootBucket(common.NewInBucket(3, 0))
|
||||
m.SetPgid(4)
|
||||
m.SetTxid(common.Txid(i))
|
||||
m.SetChecksum(m.Sum64())
|
||||
}
|
||||
|
||||
// Write an empty freelist at page 3.
|
||||
p := db.pageInBuffer(buf, pgid(2))
|
||||
p.id = pgid(2)
|
||||
p.flags = freelistPageFlag
|
||||
p.count = 0
|
||||
p := db.pageInBuffer(buf, common.Pgid(2))
|
||||
p.SetId(2)
|
||||
p.SetFlags(common.FreelistPageFlag)
|
||||
p.SetCount(0)
|
||||
|
||||
// Write an empty leaf page at page 4.
|
||||
p = db.pageInBuffer(buf, pgid(3))
|
||||
p.id = pgid(3)
|
||||
p.flags = leafPageFlag
|
||||
p.count = 0
|
||||
p = db.pageInBuffer(buf, common.Pgid(3))
|
||||
p.SetId(3)
|
||||
p.SetFlags(common.LeafPageFlag)
|
||||
p.SetCount(0)
|
||||
|
||||
// Write the buffer to our data file.
|
||||
if _, err := db.ops.writeAt(buf, 0); err != nil {
|
||||
db.Logger().Errorf("writeAt failed: %w", err)
|
||||
return err
|
||||
}
|
||||
if err := fdatasync(db); err != nil {
|
||||
db.Logger().Errorf("[GOOS: %s, GOARCH: %s] fdatasync failed: %w", runtime.GOOS, runtime.GOARCH, err)
|
||||
return err
|
||||
}
|
||||
db.filesz = len(buf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -716,13 +740,31 @@ func (db *DB) close() error {
|
|||
//
|
||||
// IMPORTANT: You must close read-only transactions after you are finished or
|
||||
// else the database will not reclaim old pages.
|
||||
func (db *DB) Begin(writable bool) (*Tx, error) {
|
||||
func (db *DB) Begin(writable bool) (t *Tx, err error) {
|
||||
if lg := db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Starting a new transaction [writable: %t]", writable)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("Starting a new transaction [writable: %t] failed: %v", writable, err)
|
||||
} else {
|
||||
lg.Debugf("Starting a new transaction [writable: %t] successfully", writable)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if writable {
|
||||
return db.beginRWTx()
|
||||
}
|
||||
return db.beginTx()
|
||||
}
|
||||
|
||||
func (db *DB) Logger() Logger {
|
||||
if db == nil || db.logger == nil {
|
||||
return getDiscardLogger()
|
||||
}
|
||||
return db.logger
|
||||
}
|
||||
|
||||
func (db *DB) beginTx() (*Tx, error) {
|
||||
// Lock the meta pages while we initialize the transaction. We obtain
|
||||
// the meta lock before the mmap lock because that's the order that the
|
||||
|
|
@ -738,14 +780,14 @@ func (db *DB) beginTx() (*Tx, error) {
|
|||
if !db.opened {
|
||||
db.mmaplock.RUnlock()
|
||||
db.metalock.Unlock()
|
||||
return nil, ErrDatabaseNotOpen
|
||||
return nil, berrors.ErrDatabaseNotOpen
|
||||
}
|
||||
|
||||
// Exit if the database is not correctly mapped.
|
||||
if db.data == nil {
|
||||
db.mmaplock.RUnlock()
|
||||
db.metalock.Unlock()
|
||||
return nil, ErrInvalidMapping
|
||||
return nil, berrors.ErrInvalidMapping
|
||||
}
|
||||
|
||||
// Create a transaction associated with the database.
|
||||
|
|
@ -755,6 +797,9 @@ func (db *DB) beginTx() (*Tx, error) {
|
|||
// Keep track of transaction until it closes.
|
||||
db.txs = append(db.txs, t)
|
||||
n := len(db.txs)
|
||||
if db.freelist != nil {
|
||||
db.freelist.AddReadonlyTXID(t.meta.Txid())
|
||||
}
|
||||
|
||||
// Unlock the meta pages.
|
||||
db.metalock.Unlock()
|
||||
|
|
@ -771,7 +816,7 @@ func (db *DB) beginTx() (*Tx, error) {
|
|||
func (db *DB) beginRWTx() (*Tx, error) {
|
||||
// If the database was opened with Options.ReadOnly, return an error.
|
||||
if db.readOnly {
|
||||
return nil, ErrDatabaseReadOnly
|
||||
return nil, berrors.ErrDatabaseReadOnly
|
||||
}
|
||||
|
||||
// Obtain writer lock. This is released by the transaction when it closes.
|
||||
|
|
@ -786,49 +831,23 @@ func (db *DB) beginRWTx() (*Tx, error) {
|
|||
// Exit if the database is not open yet.
|
||||
if !db.opened {
|
||||
db.rwlock.Unlock()
|
||||
return nil, ErrDatabaseNotOpen
|
||||
return nil, berrors.ErrDatabaseNotOpen
|
||||
}
|
||||
|
||||
// Exit if the database is not correctly mapped.
|
||||
if db.data == nil {
|
||||
db.rwlock.Unlock()
|
||||
return nil, ErrInvalidMapping
|
||||
return nil, berrors.ErrInvalidMapping
|
||||
}
|
||||
|
||||
// Create a transaction associated with the database.
|
||||
t := &Tx{writable: true}
|
||||
t.init(db)
|
||||
db.rwtx = t
|
||||
db.freePages()
|
||||
db.freelist.ReleasePendingPages()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// freePages releases any pages associated with closed read-only transactions.
|
||||
func (db *DB) freePages() {
|
||||
// Free all pending pages prior to earliest open transaction.
|
||||
sort.Sort(txsById(db.txs))
|
||||
minid := txid(0xFFFFFFFFFFFFFFFF)
|
||||
if len(db.txs) > 0 {
|
||||
minid = db.txs[0].meta.txid
|
||||
}
|
||||
if minid > 0 {
|
||||
db.freelist.release(minid - 1)
|
||||
}
|
||||
// Release unused txid extents.
|
||||
for _, t := range db.txs {
|
||||
db.freelist.releaseRange(minid, t.meta.txid-1)
|
||||
minid = t.meta.txid + 1
|
||||
}
|
||||
db.freelist.releaseRange(minid, txid(0xFFFFFFFFFFFFFFFF))
|
||||
// Any page both allocated and freed in an extent is safe to release.
|
||||
}
|
||||
|
||||
type txsById []*Tx
|
||||
|
||||
func (t txsById) Len() int { return len(t) }
|
||||
func (t txsById) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t txsById) Less(i, j int) bool { return t[i].meta.txid < t[j].meta.txid }
|
||||
|
||||
// removeTx removes a transaction from the database.
|
||||
func (db *DB) removeTx(tx *Tx) {
|
||||
// Release the read lock on the mmap.
|
||||
|
|
@ -848,6 +867,9 @@ func (db *DB) removeTx(tx *Tx) {
|
|||
}
|
||||
}
|
||||
n := len(db.txs)
|
||||
if db.freelist != nil {
|
||||
db.freelist.RemoveReadonlyTXID(tx.meta.Txid())
|
||||
}
|
||||
|
||||
// Unlock the meta pages.
|
||||
db.metalock.Unlock()
|
||||
|
|
@ -1056,7 +1078,20 @@ func safelyCall(fn func(*Tx) error, tx *Tx) (err error) {
|
|||
//
|
||||
// This is not necessary under normal operation, however, if you use NoSync
|
||||
// then it allows you to force the database file to sync against the disk.
|
||||
func (db *DB) Sync() error { return fdatasync(db) }
|
||||
func (db *DB) Sync() (err error) {
|
||||
if lg := db.Logger(); lg != discardLogger {
|
||||
lg.Debugf("Syncing bbolt db (%s)", db.path)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing bbolt db (%s) failed: %v", runtime.GOOS, runtime.GOARCH, db.path, err)
|
||||
} else {
|
||||
lg.Debugf("Syncing bbolt db (%s) successfully", db.path)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return fdatasync(db)
|
||||
}
|
||||
|
||||
// Stats retrieves ongoing performance stats for the database.
|
||||
// This is only updated when a transaction closes.
|
||||
|
|
@ -1069,37 +1104,37 @@ func (db *DB) Stats() Stats {
|
|||
// This is for internal access to the raw data bytes from the C cursor, use
|
||||
// carefully, or not at all.
|
||||
func (db *DB) Info() *Info {
|
||||
_assert(db.data != nil, "database file isn't correctly mapped")
|
||||
common.Assert(db.data != nil, "database file isn't correctly mapped")
|
||||
return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
|
||||
}
|
||||
|
||||
// page retrieves a page reference from the mmap based on the current page size.
|
||||
func (db *DB) page(id pgid) *page {
|
||||
pos := id * pgid(db.pageSize)
|
||||
return (*page)(unsafe.Pointer(&db.data[pos]))
|
||||
func (db *DB) page(id common.Pgid) *common.Page {
|
||||
pos := id * common.Pgid(db.pageSize)
|
||||
return (*common.Page)(unsafe.Pointer(&db.data[pos]))
|
||||
}
|
||||
|
||||
// pageInBuffer retrieves a page reference from a given byte array based on the current page size.
|
||||
func (db *DB) pageInBuffer(b []byte, id pgid) *page {
|
||||
return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)]))
|
||||
func (db *DB) pageInBuffer(b []byte, id common.Pgid) *common.Page {
|
||||
return (*common.Page)(unsafe.Pointer(&b[id*common.Pgid(db.pageSize)]))
|
||||
}
|
||||
|
||||
// meta retrieves the current meta page reference.
|
||||
func (db *DB) meta() *meta {
|
||||
func (db *DB) meta() *common.Meta {
|
||||
// We have to return the meta with the highest txid which doesn't fail
|
||||
// validation. Otherwise, we can cause errors when in fact the database is
|
||||
// in a consistent state. metaA is the one with the higher txid.
|
||||
metaA := db.meta0
|
||||
metaB := db.meta1
|
||||
if db.meta1.txid > db.meta0.txid {
|
||||
if db.meta1.Txid() > db.meta0.Txid() {
|
||||
metaA = db.meta1
|
||||
metaB = db.meta0
|
||||
}
|
||||
|
||||
// Use higher meta page if valid. Otherwise, fallback to previous, if valid.
|
||||
if err := metaA.validate(); err == nil {
|
||||
if err := metaA.Validate(); err == nil {
|
||||
return metaA
|
||||
} else if err := metaB.validate(); err == nil {
|
||||
} else if err := metaB.Validate(); err == nil {
|
||||
return metaB
|
||||
}
|
||||
|
||||
|
|
@ -1109,7 +1144,7 @@ func (db *DB) meta() *meta {
|
|||
}
|
||||
|
||||
// allocate returns a contiguous block of memory starting at a given page.
|
||||
func (db *DB) allocate(txid txid, count int) (*page, error) {
|
||||
func (db *DB) allocate(txid common.Txid, count int) (*common.Page, error) {
|
||||
// Allocate a temporary buffer for the page.
|
||||
var buf []byte
|
||||
if count == 1 {
|
||||
|
|
@ -1117,17 +1152,18 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
|||
} else {
|
||||
buf = make([]byte, count*db.pageSize)
|
||||
}
|
||||
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||
p.overflow = uint32(count - 1)
|
||||
p := (*common.Page)(unsafe.Pointer(&buf[0]))
|
||||
p.SetOverflow(uint32(count - 1))
|
||||
|
||||
// Use pages from the freelist if they are available.
|
||||
if p.id = db.freelist.allocate(txid, count); p.id != 0 {
|
||||
p.SetId(db.freelist.Allocate(txid, count))
|
||||
if p.Id() != 0 {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Resize mmap() if we're at the end.
|
||||
p.id = db.rwtx.meta.pgid
|
||||
var minsz = int((p.id+pgid(count))+1) * db.pageSize
|
||||
p.SetId(db.rwtx.meta.Pgid())
|
||||
var minsz = int((p.Id()+common.Pgid(count))+1) * db.pageSize
|
||||
if minsz >= db.datasz {
|
||||
if err := db.mmap(minsz); err != nil {
|
||||
return nil, fmt.Errorf("mmap allocate error: %s", err)
|
||||
|
|
@ -1135,7 +1171,8 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
|||
}
|
||||
|
||||
// Move the page id high water mark.
|
||||
db.rwtx.meta.pgid += pgid(count)
|
||||
curPgid := db.rwtx.meta.Pgid()
|
||||
db.rwtx.meta.SetPgid(curPgid + common.Pgid(count))
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
|
@ -1143,7 +1180,13 @@ func (db *DB) allocate(txid txid, count int) (*page, error) {
|
|||
// grow grows the size of the database to the given sz.
|
||||
func (db *DB) grow(sz int) error {
|
||||
// Ignore if the new size is less than available file size.
|
||||
if sz <= db.filesz {
|
||||
lg := db.Logger()
|
||||
fileSize, err := db.fileSize()
|
||||
if err != nil {
|
||||
lg.Errorf("getting file size failed: %w", err)
|
||||
return err
|
||||
}
|
||||
if sz <= fileSize {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1162,21 +1205,22 @@ func (db *DB) grow(sz int) error {
|
|||
// gofail: var resizeFileError string
|
||||
// return errors.New(resizeFileError)
|
||||
if err := db.file.Truncate(int64(sz)); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] truncating file failed, size: %d, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, sz, db.datasz, err)
|
||||
return fmt.Errorf("file resize error: %s", err)
|
||||
}
|
||||
}
|
||||
if err := db.file.Sync(); err != nil {
|
||||
lg.Errorf("[GOOS: %s, GOARCH: %s] syncing file failed, db.datasz: %d, error: %v", runtime.GOOS, runtime.GOARCH, db.datasz, err)
|
||||
return fmt.Errorf("file sync error: %s", err)
|
||||
}
|
||||
if db.Mlock {
|
||||
// unlock old file and lock new one
|
||||
if err := db.mrelock(db.filesz, sz); err != nil {
|
||||
if err := db.mrelock(fileSize, sz); err != nil {
|
||||
return fmt.Errorf("mlock/munlock error: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.filesz = sz
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -1184,7 +1228,7 @@ func (db *DB) IsReadOnly() bool {
|
|||
return db.readOnly
|
||||
}
|
||||
|
||||
func (db *DB) freepages() []pgid {
|
||||
func (db *DB) freepages() []common.Pgid {
|
||||
tx, err := db.beginTx()
|
||||
defer func() {
|
||||
err = tx.Rollback()
|
||||
|
|
@ -1196,21 +1240,21 @@ func (db *DB) freepages() []pgid {
|
|||
panic("freepages: failed to open read only tx")
|
||||
}
|
||||
|
||||
reachable := make(map[pgid]*page)
|
||||
nofreed := make(map[pgid]bool)
|
||||
reachable := make(map[common.Pgid]*common.Page)
|
||||
nofreed := make(map[common.Pgid]bool)
|
||||
ech := make(chan error)
|
||||
go func() {
|
||||
for e := range ech {
|
||||
panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e))
|
||||
}
|
||||
}()
|
||||
tx.checkBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
|
||||
tx.recursivelyCheckBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech)
|
||||
close(ech)
|
||||
|
||||
// TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages.
|
||||
|
||||
var fids []pgid
|
||||
for i := pgid(2); i < db.meta().pgid; i++ {
|
||||
var fids []common.Pgid
|
||||
for i := common.Pgid(2); i < db.meta().Pgid(); i++ {
|
||||
if _, ok := reachable[i]; !ok {
|
||||
fids = append(fids, i)
|
||||
}
|
||||
|
|
@ -1218,11 +1262,17 @@ func (db *DB) freepages() []pgid {
|
|||
return fids
|
||||
}
|
||||
|
||||
func newFreelist(freelistType FreelistType) fl.Interface {
|
||||
if freelistType == FreelistMapType {
|
||||
return fl.NewHashMapFreelist()
|
||||
}
|
||||
return fl.NewArrayFreelist()
|
||||
}
|
||||
|
||||
// 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.
|
||||
// When set to zero it will wait indefinitely.
|
||||
Timeout time.Duration
|
||||
|
||||
// Sets the DB.NoGrowSync flag before memory mapping the file.
|
||||
|
|
@ -1259,6 +1309,12 @@ type Options struct {
|
|||
// If <=0, the initial map size is 0.
|
||||
// If initialMmapSize is smaller than the previous database size,
|
||||
// it takes no effect.
|
||||
//
|
||||
// Note: On Windows, due to platform limitations, the database file size
|
||||
// will be immediately resized to match `InitialMmapSize` (aligned to page size)
|
||||
// when the DB is opened. On non-Windows platforms, the file size will grow
|
||||
// dynamically based on the actual amount of written data, regardless of `InitialMmapSize`.
|
||||
// Refer to https://github.com/etcd-io/bbolt/issues/378#issuecomment-1378121966.
|
||||
InitialMmapSize int
|
||||
|
||||
// PageSize overrides the default OS page size.
|
||||
|
|
@ -1277,6 +1333,19 @@ type Options struct {
|
|||
// It prevents potential page faults, however
|
||||
// used memory can't be reclaimed. (UNIX only)
|
||||
Mlock bool
|
||||
|
||||
// Logger is the logger used for bbolt.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
func (o *Options) String() string {
|
||||
if o == nil {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("{Timeout: %s, NoGrowSync: %t, NoFreelistSync: %t, PreLoadFreelist: %t, FreelistType: %s, ReadOnly: %t, MmapFlags: %x, InitialMmapSize: %d, PageSize: %d, NoSync: %t, OpenFile: %p, Mlock: %t, Logger: %p}",
|
||||
o.Timeout, o.NoGrowSync, o.NoFreelistSync, o.PreLoadFreelist, o.FreelistType, o.ReadOnly, o.MmapFlags, o.InitialMmapSize, o.PageSize, o.NoSync, o.OpenFile, o.Mlock, o.Logger)
|
||||
|
||||
}
|
||||
|
||||
// DefaultOptions represent the options used if nil options are passed into Open().
|
||||
|
|
@ -1327,65 +1396,3 @@ type Info struct {
|
|||
Data uintptr
|
||||
PageSize int
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
magic uint32
|
||||
version uint32
|
||||
pageSize uint32
|
||||
flags uint32
|
||||
root bucket
|
||||
freelist pgid
|
||||
pgid pgid
|
||||
txid txid
|
||||
checksum uint64
|
||||
}
|
||||
|
||||
// validate checks the marker bytes and version of the meta page to ensure it matches this binary.
|
||||
func (m *meta) validate() error {
|
||||
if m.magic != magic {
|
||||
return ErrInvalid
|
||||
} else if m.version != version {
|
||||
return ErrVersionMismatch
|
||||
} else if m.checksum != m.sum64() {
|
||||
return ErrChecksum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy copies one meta object to another.
|
||||
func (m *meta) copy(dest *meta) {
|
||||
*dest = *m
|
||||
}
|
||||
|
||||
// write writes the meta onto a page.
|
||||
func (m *meta) write(p *page) {
|
||||
if m.root.root >= m.pgid {
|
||||
panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid))
|
||||
} else if m.freelist >= m.pgid && m.freelist != pgidNoFreelist {
|
||||
// TODO: reject pgidNoFreeList if !NoFreelistSync
|
||||
panic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid))
|
||||
}
|
||||
|
||||
// Page id is either going to be 0 or 1 which we can determine by the transaction ID.
|
||||
p.id = pgid(m.txid % 2)
|
||||
p.flags |= metaPageFlag
|
||||
|
||||
// Calculate the checksum.
|
||||
m.checksum = m.sum64()
|
||||
|
||||
m.copy(p.meta())
|
||||
}
|
||||
|
||||
// generates the checksum for the meta.
|
||||
func (m *meta) sum64() uint64 {
|
||||
var h = fnv.New64a()
|
||||
_, _ = h.Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer(m))[:])
|
||||
return h.Sum64()
|
||||
}
|
||||
|
||||
// _assert will panic with a given formatted message if the given condition is false.
|
||||
func _assert(condition bool, msg string, v ...interface{}) {
|
||||
if !condition {
|
||||
panic(fmt.Sprintf("assertion failed: "+msg, v...))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue