2014-01-08 15:06:17 +00:00
|
|
|
package bolt
|
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
import (
|
2014-02-16 19:11:10 +00:00
|
|
|
"fmt"
|
2014-05-05 13:56:54 +00:00
|
|
|
"hash/fnv"
|
2014-01-12 22:30:09 +00:00
|
|
|
"os"
|
2014-06-18 22:16:58 +00:00
|
|
|
"runtime/debug"
|
|
|
|
"strings"
|
2014-01-12 05:51:01 +00:00
|
|
|
"sync"
|
2014-06-21 20:44:22 +00:00
|
|
|
"time"
|
2014-01-12 05:51:01 +00:00
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
2014-02-12 21:57:27 +00:00
|
|
|
// The smallest size that the mmap can be.
|
2014-02-11 19:16:12 +00:00
|
|
|
const minMmapSize = 1 << 22 // 4MB
|
2014-02-12 21:57:27 +00:00
|
|
|
|
|
|
|
// The largest step that can be taken when remapping the mmap.
|
2014-02-11 19:16:12 +00:00
|
|
|
const maxMmapStep = 1 << 30 // 1GB
|
|
|
|
|
2014-05-05 13:56:54 +00:00
|
|
|
// 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
|
|
|
|
|
2014-02-12 21:57:27 +00:00
|
|
|
// DB represents a collection of buckets persisted to a file on disk.
|
2014-02-23 05:54:54 +00:00
|
|
|
// All data access is performed through transactions which can be obtained through the DB.
|
|
|
|
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
|
2014-01-12 05:51:01 +00:00
|
|
|
type DB struct {
|
2014-05-15 00:08:55 +00:00
|
|
|
// When enabled, the database will perform a Check() after every commit.
|
|
|
|
// A panic is issued if the database is in an inconsistent state. This
|
|
|
|
// flag has a large performance impact so it should only be used for
|
|
|
|
// debugging purposes.
|
|
|
|
StrictMode bool
|
|
|
|
|
2014-07-15 13:37:46 +00:00
|
|
|
// Setting the NoSync flag will cause the database to skip fsync()
|
|
|
|
// calls after each commit. This can be useful when bulk loading data
|
|
|
|
// into a database and you can restart the bulk load in the event of
|
|
|
|
// a system failure or database corruption. Do not set this flag for
|
|
|
|
// normal use.
|
|
|
|
//
|
|
|
|
// THIS IS UNSAFE. PLEASE USE WITH CAUTION.
|
|
|
|
NoSync bool
|
|
|
|
|
2014-03-09 00:01:49 +00:00
|
|
|
path string
|
2014-03-21 19:21:33 +00:00
|
|
|
file *os.File
|
2014-06-12 15:23:30 +00:00
|
|
|
dataref []byte
|
|
|
|
data *[maxMapSize]byte
|
|
|
|
datasz int
|
2014-03-09 00:01:49 +00:00
|
|
|
meta0 *meta
|
|
|
|
meta1 *meta
|
|
|
|
pageSize int
|
|
|
|
opened bool
|
2014-03-09 03:25:37 +00:00
|
|
|
rwtx *Tx
|
2014-03-09 00:01:49 +00:00
|
|
|
txs []*Tx
|
|
|
|
freelist *freelist
|
2014-04-02 21:36:53 +00:00
|
|
|
stats Stats
|
2014-02-09 22:52:19 +00:00
|
|
|
|
|
|
|
rwlock sync.Mutex // Allows only one writer at a time.
|
|
|
|
metalock sync.Mutex // Protects meta page access.
|
|
|
|
mmaplock sync.RWMutex // Protects mmap access during remapping.
|
2014-04-07 22:24:51 +00:00
|
|
|
statlock sync.RWMutex // Protects stats access.
|
2014-03-23 21:40:08 +00:00
|
|
|
|
|
|
|
ops struct {
|
2014-04-02 19:50:03 +00:00
|
|
|
writeAt func(b []byte, off int64) (n int, err error)
|
2014-03-23 21:40:08 +00:00
|
|
|
}
|
2014-01-12 05:51:01 +00:00
|
|
|
}
|
|
|
|
|
2014-01-24 19:51:56 +00:00
|
|
|
// Path returns the path to currently open database file.
|
2014-01-12 05:51:01 +00:00
|
|
|
func (db *DB) Path() string {
|
2014-01-09 16:07:10 +00:00
|
|
|
return db.path
|
|
|
|
}
|
|
|
|
|
2014-02-23 05:54:54 +00:00
|
|
|
// GoString returns the Go string representation of the database.
|
|
|
|
func (db *DB) GoString() string {
|
|
|
|
return fmt.Sprintf("bolt.DB{path:%q}", db.path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the string representation of the database.
|
|
|
|
func (db *DB) String() string {
|
|
|
|
return fmt.Sprintf("DB<%q>", db.path)
|
|
|
|
}
|
|
|
|
|
2014-03-31 17:22:27 +00:00
|
|
|
// Open creates and opens a database at the given path.
|
2014-01-24 19:51:56 +00:00
|
|
|
// If the file does not exist then it will be created automatically.
|
2014-06-21 20:44:22 +00:00
|
|
|
// 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) {
|
2014-07-24 16:36:09 +00:00
|
|
|
var db = &DB{opened: true}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-06-22 18:44:20 +00:00
|
|
|
// Set default options if no options are provided.
|
2014-06-21 20:44:22 +00:00
|
|
|
if options == nil {
|
2014-06-22 18:44:20 +00:00
|
|
|
options = DefaultOptions
|
2014-06-21 20:44:22 +00:00
|
|
|
}
|
|
|
|
|
2014-01-10 14:32:12 +00:00
|
|
|
// Open data file and separate sync handler for metadata writes.
|
|
|
|
db.path = path
|
2014-03-31 17:22:27 +00:00
|
|
|
|
|
|
|
var err error
|
2014-03-21 19:21:33 +00:00
|
|
|
if db.file, err = os.OpenFile(db.path, os.O_RDWR|os.O_CREATE, mode); err != nil {
|
2014-03-24 13:38:27 +00:00
|
|
|
_ = db.close()
|
2014-03-31 17:22:27 +00:00
|
|
|
return nil, err
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
|
|
|
|
2014-04-02 20:05:24 +00:00
|
|
|
// 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.
|
2014-06-21 20:44:22 +00:00
|
|
|
if err := flock(db.file, options.Timeout); err != nil {
|
2014-04-02 20:05:24 +00:00
|
|
|
_ = db.close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-03-31 17:22:27 +00:00
|
|
|
// Default values for test hooks
|
|
|
|
db.ops.writeAt = db.file.WriteAt
|
2014-03-23 21:40:08 +00:00
|
|
|
|
2014-01-28 19:52:09 +00:00
|
|
|
// Initialize the database if it doesn't exist.
|
|
|
|
if info, err := db.file.Stat(); err != nil {
|
2014-03-31 17:22:27 +00:00
|
|
|
return nil, fmt.Errorf("stat error: %s", err)
|
2014-01-28 19:52:09 +00:00
|
|
|
} else if info.Size() == 0 {
|
|
|
|
// Initialize new files with meta pages.
|
2014-01-12 05:51:01 +00:00
|
|
|
if err := db.init(); err != nil {
|
2014-03-31 17:22:27 +00:00
|
|
|
return nil, err
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
2014-01-28 19:52:09 +00:00
|
|
|
} else {
|
|
|
|
// Read the first meta page to determine the page size.
|
2014-02-12 21:57:27 +00:00
|
|
|
var buf [0x1000]byte
|
2014-01-28 19:52:09 +00:00
|
|
|
if _, err := db.file.ReadAt(buf[:], 0); err == nil {
|
2014-01-30 03:35:58 +00:00
|
|
|
m := db.pageInBuffer(buf[:], 0).meta()
|
|
|
|
if err := m.validate(); err != nil {
|
2014-04-02 22:56:16 +00:00
|
|
|
return nil, fmt.Errorf("meta0 error: %s", err)
|
2014-01-28 19:52:09 +00:00
|
|
|
}
|
2014-01-30 03:35:58 +00:00
|
|
|
db.pageSize = int(m.pageSize)
|
2014-01-28 19:52:09 +00:00
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Memory map the data file.
|
2014-02-11 19:16:12 +00:00
|
|
|
if err := db.mmap(0); err != nil {
|
2014-03-24 13:38:27 +00:00
|
|
|
_ = db.close()
|
2014-03-31 17:22:27 +00:00
|
|
|
return nil, err
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
|
|
|
|
2014-02-09 22:52:19 +00:00
|
|
|
// Read in the freelist.
|
2014-06-30 14:01:41 +00:00
|
|
|
db.freelist = newFreelist()
|
2014-02-09 22:52:19 +00:00
|
|
|
db.freelist.read(db.page(db.meta().freelist))
|
|
|
|
|
2014-01-10 14:32:12 +00:00
|
|
|
// Mark the database as opened and return.
|
2014-03-31 17:22:27 +00:00
|
|
|
return db, nil
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
|
|
|
|
2014-01-24 19:51:56 +00:00
|
|
|
// mmap opens the underlying memory-mapped file and initializes the meta references.
|
2014-02-11 19:16:12 +00:00
|
|
|
// minsz is the minimum size that the new mmap can be.
|
|
|
|
func (db *DB) mmap(minsz int) error {
|
|
|
|
db.mmaplock.Lock()
|
|
|
|
defer db.mmaplock.Unlock()
|
|
|
|
|
|
|
|
// Dereference all mmap references before unmapping.
|
2014-03-09 00:01:49 +00:00
|
|
|
if db.rwtx != nil {
|
2014-04-07 22:24:51 +00:00
|
|
|
db.rwtx.root.dereference()
|
2014-02-11 19:16:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unmap existing data before continuing.
|
2014-03-21 19:21:33 +00:00
|
|
|
if err := db.munmap(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-02-11 19:16:12 +00:00
|
|
|
|
2014-01-28 19:52:09 +00:00
|
|
|
info, err := db.file.Stat()
|
|
|
|
if err != nil {
|
2014-03-24 14:31:15 +00:00
|
|
|
return fmt.Errorf("mmap stat error: %s", err)
|
2014-01-28 20:16:22 +00:00
|
|
|
} else if int(info.Size()) < db.pageSize*2 {
|
2014-03-24 14:31:15 +00:00
|
|
|
return fmt.Errorf("file size too small")
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
2014-01-30 23:22:02 +00:00
|
|
|
|
2014-02-11 19:16:12 +00:00
|
|
|
// Ensure the size is at least the minimum size.
|
|
|
|
var size = int(info.Size())
|
|
|
|
if size < minsz {
|
|
|
|
size = minsz
|
|
|
|
}
|
2014-02-27 21:33:31 +00:00
|
|
|
size = db.mmapSize(size)
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
// Memory-map the data file as a byte slice.
|
2014-06-12 15:23:30 +00:00
|
|
|
if err := mmap(db, size); err != nil {
|
2014-01-12 05:51:01 +00:00
|
|
|
return err
|
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
// Save references to the meta pages.
|
2014-01-30 03:35:58 +00:00
|
|
|
db.meta0 = db.page(0).meta()
|
|
|
|
db.meta1 = db.page(1).meta()
|
|
|
|
|
|
|
|
// Validate the meta pages.
|
|
|
|
if err := db.meta0.validate(); err != nil {
|
2014-03-24 14:31:15 +00:00
|
|
|
return fmt.Errorf("meta0 error: %s", err)
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
2014-01-30 03:35:58 +00:00
|
|
|
if err := db.meta1.validate(); err != nil {
|
2014-03-24 14:31:15 +00:00
|
|
|
return fmt.Errorf("meta1 error: %s", err)
|
2014-01-10 14:32:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-02-11 19:16:12 +00:00
|
|
|
// munmap unmaps the data file from memory.
|
2014-03-21 19:21:33 +00:00
|
|
|
func (db *DB) munmap() error {
|
2014-06-12 15:23:30 +00:00
|
|
|
if err := munmap(db); err != nil {
|
|
|
|
return fmt.Errorf("unmap error: " + err.Error())
|
2014-02-11 19:16:12 +00:00
|
|
|
}
|
2014-03-21 19:21:33 +00:00
|
|
|
return nil
|
2014-02-11 19:16:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// mmapSize determines the appropriate size for the mmap given the current size
|
|
|
|
// of the database. The minimum size is 4MB and doubles until it reaches 1GB.
|
|
|
|
func (db *DB) mmapSize(size int) int {
|
2014-07-10 13:11:01 +00:00
|
|
|
if size <= minMmapSize {
|
2014-02-11 19:16:12 +00:00
|
|
|
return minMmapSize
|
|
|
|
} else if size < maxMmapStep {
|
|
|
|
size *= 2
|
|
|
|
} else {
|
|
|
|
size += maxMmapStep
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the mmap size is a multiple of the page size.
|
|
|
|
if (size % db.pageSize) != 0 {
|
|
|
|
size = ((size / db.pageSize) + 1) * db.pageSize
|
|
|
|
}
|
|
|
|
|
|
|
|
return size
|
|
|
|
}
|
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
// init creates a new database file and initializes its meta pages.
|
|
|
|
func (db *DB) init() error {
|
2014-01-28 03:22:37 +00:00
|
|
|
// Set the page size to the OS page size.
|
2014-03-21 19:21:33 +00:00
|
|
|
db.pageSize = os.Getpagesize()
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
// Create two meta pages on a buffer.
|
2014-01-30 03:50:29 +00:00
|
|
|
buf := make([]byte, db.pageSize*4)
|
2014-01-12 05:51:01 +00:00
|
|
|
for i := 0; i < 2; i++ {
|
2014-01-27 15:11:54 +00:00
|
|
|
p := db.pageInBuffer(buf[:], pgid(i))
|
|
|
|
p.id = pgid(i)
|
2014-02-12 21:57:27 +00:00
|
|
|
p.flags = metaPageFlag
|
2014-01-30 03:35:58 +00:00
|
|
|
|
2014-01-30 03:50:29 +00:00
|
|
|
// Initialize the meta page.
|
2014-01-30 03:35:58 +00:00
|
|
|
m := p.meta()
|
|
|
|
m.magic = magic
|
2014-01-31 17:22:58 +00:00
|
|
|
m.version = version
|
2014-01-30 03:35:58 +00:00
|
|
|
m.pageSize = uint32(db.pageSize)
|
2014-01-31 17:22:58 +00:00
|
|
|
m.version = version
|
2014-02-09 22:52:19 +00:00
|
|
|
m.freelist = 2
|
2014-04-07 22:24:51 +00:00
|
|
|
m.root = bucket{root: 3}
|
2014-01-30 23:22:02 +00:00
|
|
|
m.pgid = 4
|
2014-03-09 00:01:49 +00:00
|
|
|
m.txid = txid(i)
|
2014-01-12 05:51:01 +00:00
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-01-30 03:50:29 +00:00
|
|
|
// Write an empty freelist at page 3.
|
|
|
|
p := db.pageInBuffer(buf[:], pgid(2))
|
2014-01-30 05:11:46 +00:00
|
|
|
p.id = pgid(2)
|
2014-02-12 21:57:27 +00:00
|
|
|
p.flags = freelistPageFlag
|
2014-01-30 03:50:29 +00:00
|
|
|
p.count = 0
|
|
|
|
|
|
|
|
// Write an empty leaf page at page 4.
|
|
|
|
p = db.pageInBuffer(buf[:], pgid(3))
|
2014-01-30 05:11:46 +00:00
|
|
|
p.id = pgid(3)
|
2014-04-07 22:24:51 +00:00
|
|
|
p.flags = leafPageFlag
|
2014-01-30 03:50:29 +00:00
|
|
|
p.count = 0
|
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
// Write the buffer to our data file.
|
2014-04-02 19:50:03 +00:00
|
|
|
if _, err := db.ops.writeAt(buf, 0); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := fdatasync(db.file); err != nil {
|
2014-01-12 05:51:01 +00:00
|
|
|
return err
|
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-01-12 05:51:01 +00:00
|
|
|
return nil
|
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-02-12 21:57:27 +00:00
|
|
|
// Close releases all database resources.
|
|
|
|
// All transactions must be closed before closing the database.
|
2014-03-21 19:21:33 +00:00
|
|
|
func (db *DB) Close() error {
|
2014-02-09 22:52:19 +00:00
|
|
|
db.metalock.Lock()
|
|
|
|
defer db.metalock.Unlock()
|
2014-03-21 19:21:33 +00:00
|
|
|
return db.close()
|
2014-01-24 19:51:56 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 19:21:33 +00:00
|
|
|
func (db *DB) close() error {
|
2014-02-16 05:34:21 +00:00
|
|
|
db.opened = false
|
2014-02-16 06:38:03 +00:00
|
|
|
|
2014-02-09 22:52:19 +00:00
|
|
|
db.freelist = nil
|
2014-02-12 21:57:27 +00:00
|
|
|
db.path = ""
|
2014-02-11 19:16:12 +00:00
|
|
|
|
2014-03-31 14:52:13 +00:00
|
|
|
// Clear ops.
|
|
|
|
db.ops.writeAt = nil
|
|
|
|
|
2014-03-21 19:21:33 +00:00
|
|
|
// Close the mmap.
|
|
|
|
if err := db.munmap(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close file handles.
|
|
|
|
if db.file != nil {
|
2014-04-02 20:05:24 +00:00
|
|
|
// Unlock the file.
|
2014-06-11 17:11:21 +00:00
|
|
|
_ = funlock(db.file)
|
2014-04-02 20:05:24 +00:00
|
|
|
|
|
|
|
// Close the file descriptor.
|
2014-03-21 19:21:33 +00:00
|
|
|
if err := db.file.Close(); err != nil {
|
2014-04-02 19:50:03 +00:00
|
|
|
return fmt.Errorf("db file close: %s", err)
|
2014-03-21 19:21:33 +00:00
|
|
|
}
|
|
|
|
db.file = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2014-01-12 05:51:01 +00:00
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-03-24 17:43:06 +00:00
|
|
|
// Begin starts a new transaction.
|
|
|
|
// Multiple read-only transactions can be used concurrently but only one
|
|
|
|
// write transaction can be used at a time. Starting multiple write transactions
|
|
|
|
// will cause the calls to block and be serialized until the current write
|
|
|
|
// transaction finishes.
|
2014-02-12 21:57:27 +00:00
|
|
|
//
|
2014-03-24 17:43:06 +00:00
|
|
|
// 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) {
|
|
|
|
if writable {
|
|
|
|
return db.beginRWTx()
|
|
|
|
}
|
|
|
|
return db.beginTx()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (db *DB) beginTx() (*Tx, error) {
|
2014-06-13 18:07:37 +00:00
|
|
|
// 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
|
|
|
|
// write transaction will obtain them.
|
|
|
|
db.metalock.Lock()
|
|
|
|
|
2014-02-11 19:16:12 +00:00
|
|
|
// Obtain a read-only lock on the mmap. When the mmap is remapped it will
|
|
|
|
// obtain a write lock so all transactions must finish before it can be
|
|
|
|
// remapped.
|
|
|
|
db.mmaplock.RLock()
|
|
|
|
|
2014-01-13 17:35:04 +00:00
|
|
|
// Exit if the database is not open yet.
|
|
|
|
if !db.opened {
|
2014-04-07 22:24:51 +00:00
|
|
|
db.mmaplock.RUnlock()
|
2014-06-13 18:07:37 +00:00
|
|
|
db.metalock.Unlock()
|
2014-02-16 19:18:44 +00:00
|
|
|
return nil, ErrDatabaseNotOpen
|
2014-01-13 17:35:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a transaction associated with the database.
|
2014-03-09 00:01:49 +00:00
|
|
|
t := &Tx{}
|
2014-01-30 23:22:02 +00:00
|
|
|
t.init(db)
|
2014-01-14 20:01:02 +00:00
|
|
|
|
2014-02-09 22:52:19 +00:00
|
|
|
// Keep track of transaction until it closes.
|
2014-03-09 00:01:49 +00:00
|
|
|
db.txs = append(db.txs, t)
|
2014-05-19 18:08:33 +00:00
|
|
|
n := len(db.txs)
|
|
|
|
|
|
|
|
// Unlock the meta pages.
|
|
|
|
db.metalock.Unlock()
|
|
|
|
|
|
|
|
// Update the transaction stats.
|
|
|
|
db.statlock.Lock()
|
|
|
|
db.stats.TxN++
|
|
|
|
db.stats.OpenTxN = n
|
|
|
|
db.statlock.Unlock()
|
2014-02-09 22:52:19 +00:00
|
|
|
|
2014-01-24 19:51:56 +00:00
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2014-03-24 17:43:06 +00:00
|
|
|
func (db *DB) beginRWTx() (*Tx, error) {
|
2014-03-09 03:25:37 +00:00
|
|
|
// Obtain writer lock. This is released by the transaction when it closes.
|
2014-04-07 22:24:51 +00:00
|
|
|
// This enforces only one writer transaction at a time.
|
2014-02-09 22:52:19 +00:00
|
|
|
db.rwlock.Lock()
|
2014-01-24 19:51:56 +00:00
|
|
|
|
2014-04-07 22:24:51 +00:00
|
|
|
// Once we have the writer lock then we can lock the meta pages so that
|
|
|
|
// we can set up the transaction.
|
|
|
|
db.metalock.Lock()
|
|
|
|
defer db.metalock.Unlock()
|
|
|
|
|
2014-01-26 22:29:06 +00:00
|
|
|
// Exit if the database is not open yet.
|
|
|
|
if !db.opened {
|
2014-02-09 22:52:19 +00:00
|
|
|
db.rwlock.Unlock()
|
2014-02-16 19:18:44 +00:00
|
|
|
return nil, ErrDatabaseNotOpen
|
2014-01-13 17:35:04 +00:00
|
|
|
}
|
|
|
|
|
2014-02-23 05:54:54 +00:00
|
|
|
// Create a transaction associated with the database.
|
2014-03-09 03:25:37 +00:00
|
|
|
t := &Tx{writable: true}
|
2014-01-30 23:22:02 +00:00
|
|
|
t.init(db)
|
2014-03-09 00:01:49 +00:00
|
|
|
db.rwtx = t
|
2014-02-09 22:52:19 +00:00
|
|
|
|
|
|
|
// Free any pages associated with closed read-only transactions.
|
2014-03-09 00:01:49 +00:00
|
|
|
var minid txid = 0xFFFFFFFFFFFFFFFF
|
|
|
|
for _, t := range db.txs {
|
2014-07-26 21:11:47 +00:00
|
|
|
if t.meta.txid < minid {
|
|
|
|
minid = t.meta.txid
|
2014-02-09 22:52:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if minid > 0 {
|
|
|
|
db.freelist.release(minid - 1)
|
|
|
|
}
|
2014-01-26 22:29:06 +00:00
|
|
|
|
2014-01-13 17:35:04 +00:00
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2014-03-09 00:01:49 +00:00
|
|
|
// removeTx removes a transaction from the database.
|
2014-04-04 18:03:04 +00:00
|
|
|
func (db *DB) removeTx(tx *Tx) {
|
2014-02-11 19:16:12 +00:00
|
|
|
// Release the read lock on the mmap.
|
|
|
|
db.mmaplock.RUnlock()
|
|
|
|
|
2014-06-13 18:07:37 +00:00
|
|
|
// Use the meta lock to restrict access to the DB object.
|
|
|
|
db.metalock.Lock()
|
|
|
|
|
2014-02-09 22:52:19 +00:00
|
|
|
// Remove the transaction.
|
2014-04-04 18:03:04 +00:00
|
|
|
for i, t := range db.txs {
|
|
|
|
if t == tx {
|
2014-03-09 00:01:49 +00:00
|
|
|
db.txs = append(db.txs[:i], db.txs[i+1:]...)
|
2014-02-09 22:52:19 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2014-06-17 19:30:10 +00:00
|
|
|
n := len(db.txs)
|
2014-04-02 21:36:53 +00:00
|
|
|
|
2014-04-07 22:24:51 +00:00
|
|
|
// Unlock the meta pages.
|
|
|
|
db.metalock.Unlock()
|
|
|
|
|
2014-04-02 21:36:53 +00:00
|
|
|
// Merge statistics.
|
2014-06-17 19:30:10 +00:00
|
|
|
db.statlock.Lock()
|
|
|
|
db.stats.OpenTxN = n
|
|
|
|
db.stats.TxStats.add(&tx.stats)
|
|
|
|
db.statlock.Unlock()
|
2014-02-09 22:52:19 +00:00
|
|
|
}
|
|
|
|
|
2014-03-24 17:43:06 +00:00
|
|
|
// Update executes a function within the context of a read-write managed transaction.
|
2014-02-15 21:54:45 +00:00
|
|
|
// If no error is returned from the function then the transaction is committed.
|
|
|
|
// If an error is returned then the entire transaction is rolled back.
|
|
|
|
// Any error that is returned from the function or returned from the commit is
|
2014-03-24 17:43:06 +00:00
|
|
|
// returned from the Update() method.
|
2014-03-23 16:34:53 +00:00
|
|
|
//
|
|
|
|
// Attempting to manually commit or rollback within the function will cause a panic.
|
2014-03-24 17:43:06 +00:00
|
|
|
func (db *DB) Update(fn func(*Tx) error) error {
|
|
|
|
t, err := db.Begin(true)
|
2014-02-15 21:54:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2014-07-11 15:54:10 +00:00
|
|
|
// Make sure the transaction rolls back in the event of a panic.
|
|
|
|
defer func() {
|
|
|
|
if t.db != nil {
|
|
|
|
t.rollback()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2014-03-23 16:34:53 +00:00
|
|
|
// Mark as a managed tx so that the inner function cannot manually commit.
|
|
|
|
t.managed = true
|
|
|
|
|
2014-02-15 21:54:45 +00:00
|
|
|
// If an error is returned from the function then rollback and return error.
|
2014-03-23 16:34:53 +00:00
|
|
|
err = fn(t)
|
|
|
|
t.managed = false
|
|
|
|
if err != nil {
|
2014-03-24 13:38:27 +00:00
|
|
|
_ = t.Rollback()
|
2014-02-15 21:54:45 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t.Commit()
|
|
|
|
}
|
|
|
|
|
2014-03-24 17:43:06 +00:00
|
|
|
// View executes a function within the context of a managed read-only transaction.
|
|
|
|
// Any error that is returned from the function is returned from the View() method.
|
2014-03-23 16:34:53 +00:00
|
|
|
//
|
|
|
|
// Attempting to manually rollback within the function will cause a panic.
|
2014-03-24 17:43:06 +00:00
|
|
|
func (db *DB) View(fn func(*Tx) error) error {
|
|
|
|
t, err := db.Begin(false)
|
2014-02-16 20:51:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-02-16 22:43:35 +00:00
|
|
|
|
2014-07-11 15:54:10 +00:00
|
|
|
// Make sure the transaction rolls back in the event of a panic.
|
|
|
|
defer func() {
|
|
|
|
if t.db != nil {
|
|
|
|
t.rollback()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2014-03-23 16:34:53 +00:00
|
|
|
// Mark as a managed tx so that the inner function cannot manually rollback.
|
|
|
|
t.managed = true
|
|
|
|
|
2014-02-16 22:43:35 +00:00
|
|
|
// If an error is returned from the function then pass it through.
|
2014-03-23 16:34:53 +00:00
|
|
|
err = fn(t)
|
|
|
|
t.managed = false
|
2014-03-24 13:38:27 +00:00
|
|
|
if err != nil {
|
|
|
|
_ = t.Rollback()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.Rollback(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-03-23 16:34:53 +00:00
|
|
|
|
2014-03-24 13:38:27 +00:00
|
|
|
return nil
|
2014-02-16 22:43:35 +00:00
|
|
|
}
|
|
|
|
|
2014-04-02 21:36:53 +00:00
|
|
|
// Stats retrieves ongoing performance stats for the database.
|
|
|
|
// This is only updated when a transaction closes.
|
|
|
|
func (db *DB) Stats() Stats {
|
2014-04-07 22:24:51 +00:00
|
|
|
db.statlock.RLock()
|
|
|
|
defer db.statlock.RUnlock()
|
2014-04-02 21:36:53 +00:00
|
|
|
return db.stats
|
2014-02-21 16:49:15 +00:00
|
|
|
}
|
|
|
|
|
2014-04-15 17:56:53 +00:00
|
|
|
// This is for internal access to the raw data bytes from the C cursor, use
|
|
|
|
// carefully, or not at all.
|
2014-04-16 15:00:26 +00:00
|
|
|
func (db *DB) Info() *Info {
|
2014-06-12 15:23:30 +00:00
|
|
|
return &Info{uintptr(unsafe.Pointer(&db.data[0])), db.pageSize}
|
2014-04-15 17:56:53 +00:00
|
|
|
}
|
|
|
|
|
2014-01-24 19:51:56 +00:00
|
|
|
// page retrieves a page reference from the mmap based on the current page size.
|
2014-01-27 15:11:54 +00:00
|
|
|
func (db *DB) page(id pgid) *page {
|
2014-03-01 19:53:05 +00:00
|
|
|
pos := id * pgid(db.pageSize)
|
2014-03-01 16:13:59 +00:00
|
|
|
return (*page)(unsafe.Pointer(&db.data[pos]))
|
2014-01-24 19:51:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// pageInBuffer retrieves a page reference from a given byte array based on the current page size.
|
2014-01-27 15:11:54 +00:00
|
|
|
func (db *DB) pageInBuffer(b []byte, id pgid) *page {
|
|
|
|
return (*page)(unsafe.Pointer(&b[id*pgid(db.pageSize)]))
|
2014-01-12 05:51:01 +00:00
|
|
|
}
|
2014-01-10 14:32:12 +00:00
|
|
|
|
2014-01-14 20:01:02 +00:00
|
|
|
// meta retrieves the current meta page reference.
|
|
|
|
func (db *DB) meta() *meta {
|
2014-03-09 00:01:49 +00:00
|
|
|
if db.meta0.txid > db.meta1.txid {
|
2014-01-14 20:01:02 +00:00
|
|
|
return db.meta0
|
|
|
|
}
|
|
|
|
return db.meta1
|
|
|
|
}
|
|
|
|
|
2014-02-09 22:52:19 +00:00
|
|
|
// allocate returns a contiguous block of memory starting at a given page.
|
2014-02-11 19:16:12 +00:00
|
|
|
func (db *DB) allocate(count int) (*page, error) {
|
2014-02-09 22:52:19 +00:00
|
|
|
// Allocate a temporary buffer for the page.
|
|
|
|
buf := make([]byte, count*db.pageSize)
|
|
|
|
p := (*page)(unsafe.Pointer(&buf[0]))
|
|
|
|
p.overflow = uint32(count - 1)
|
|
|
|
|
|
|
|
// Use pages from the freelist if they are available.
|
|
|
|
if p.id = db.freelist.allocate(count); p.id != 0 {
|
2014-02-11 19:16:12 +00:00
|
|
|
return p, nil
|
2014-02-09 22:52:19 +00:00
|
|
|
}
|
|
|
|
|
2014-02-11 19:16:12 +00:00
|
|
|
// Resize mmap() if we're at the end.
|
2014-03-09 00:01:49 +00:00
|
|
|
p.id = db.rwtx.meta.pgid
|
2014-02-11 19:16:12 +00:00
|
|
|
var minsz = int((p.id+pgid(count))+1) * db.pageSize
|
2014-06-12 15:23:30 +00:00
|
|
|
if minsz >= db.datasz {
|
2014-02-11 19:16:12 +00:00
|
|
|
if err := db.mmap(minsz); err != nil {
|
2014-03-24 14:31:15 +00:00
|
|
|
return nil, fmt.Errorf("mmap allocate error: %s", err)
|
2014-02-11 19:16:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move the page id high water mark.
|
2014-03-09 00:01:49 +00:00
|
|
|
db.rwtx.meta.pgid += pgid(count)
|
2014-02-09 22:52:19 +00:00
|
|
|
|
2014-02-11 19:16:12 +00:00
|
|
|
return p, nil
|
2014-02-09 22:52:19 +00:00
|
|
|
}
|
2014-02-21 16:49:15 +00:00
|
|
|
|
2014-06-21 20:44:22 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-06-22 18:44:20 +00:00
|
|
|
// DefaultOptions represent the options used if nil options are passed into Open().
|
|
|
|
// No timeout is used which will cause Bolt to wait indefinitely for a lock.
|
|
|
|
var DefaultOptions = &Options{
|
|
|
|
Timeout: 0,
|
|
|
|
}
|
|
|
|
|
2014-04-02 21:36:53 +00:00
|
|
|
// Stats represents statistics about the database.
|
|
|
|
type Stats struct {
|
2014-06-17 18:40:56 +00:00
|
|
|
// Freelist stats
|
2014-06-20 14:53:25 +00:00
|
|
|
FreePageN int // total number of free pages on the freelist
|
|
|
|
PendingPageN int // total number of pending pages on the freelist
|
2014-06-18 18:10:42 +00:00
|
|
|
FreeAlloc int // total bytes allocated in free pages
|
|
|
|
FreelistInuse int // total bytes used by the freelist
|
2014-06-17 18:40:56 +00:00
|
|
|
|
2014-05-19 18:08:33 +00:00
|
|
|
// Transaction stats
|
2014-06-17 18:40:56 +00:00
|
|
|
TxN int // total number of started read transactions
|
2014-05-19 18:08:33 +00:00
|
|
|
OpenTxN int // number of currently open read transactions
|
|
|
|
|
2014-04-02 21:36:53 +00:00
|
|
|
TxStats TxStats // global, ongoing stats.
|
|
|
|
}
|
2014-02-21 16:49:15 +00:00
|
|
|
|
2014-04-02 21:36:53 +00:00
|
|
|
// Sub calculates and returns the difference between two sets of database stats.
|
|
|
|
// This is useful when obtaining stats at two different points and time and
|
|
|
|
// you need the performance counters that occurred within that time span.
|
|
|
|
func (s *Stats) Sub(other *Stats) Stats {
|
2014-06-24 15:30:28 +00:00
|
|
|
if other == nil {
|
2014-06-24 17:54:40 +00:00
|
|
|
return *s
|
2014-06-24 15:30:28 +00:00
|
|
|
}
|
2014-06-24 17:58:46 +00:00
|
|
|
var diff Stats
|
2014-06-27 17:38:06 +00:00
|
|
|
diff.FreePageN = s.FreePageN
|
|
|
|
diff.PendingPageN = s.PendingPageN
|
|
|
|
diff.FreeAlloc = s.FreeAlloc
|
|
|
|
diff.FreelistInuse = s.FreelistInuse
|
2014-06-24 17:54:40 +00:00
|
|
|
diff.TxN = other.TxN - s.TxN
|
2014-04-02 21:36:53 +00:00
|
|
|
diff.TxStats = s.TxStats.Sub(&other.TxStats)
|
|
|
|
return diff
|
|
|
|
}
|
2014-02-21 16:49:15 +00:00
|
|
|
|
2014-04-02 21:36:53 +00:00
|
|
|
func (s *Stats) add(other *Stats) {
|
|
|
|
s.TxStats.add(&other.TxStats)
|
2014-02-21 16:49:15 +00:00
|
|
|
}
|
2014-04-16 15:00:26 +00:00
|
|
|
|
|
|
|
type Info struct {
|
2014-06-12 15:23:30 +00:00
|
|
|
Data uintptr
|
2014-04-16 15:00:26 +00:00
|
|
|
PageSize int
|
|
|
|
}
|
2014-05-05 13:56:54 +00:00
|
|
|
|
|
|
|
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.checksum != 0 && m.checksum != m.sum64() {
|
|
|
|
return ErrChecksum
|
|
|
|
} else if m.magic != magic {
|
|
|
|
return ErrInvalid
|
|
|
|
} else if m.version != version {
|
|
|
|
return ErrVersionMismatch
|
|
|
|
}
|
|
|
|
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) {
|
2014-05-09 13:35:00 +00:00
|
|
|
|
|
|
|
_assert(m.root.root < m.pgid, "root bucket pgid (%d) above high water mark (%d)", m.root.root, m.pgid)
|
|
|
|
_assert(m.freelist < m.pgid, "freelist pgid (%d) above high water mark (%d)", m.freelist, m.pgid)
|
|
|
|
|
2014-05-05 13:56:54 +00:00
|
|
|
// 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...))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func warn(v ...interface{}) {
|
|
|
|
fmt.Fprintln(os.Stderr, v...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func warnf(msg string, v ...interface{}) {
|
|
|
|
fmt.Fprintf(os.Stderr, msg+"\n", v...)
|
|
|
|
}
|
2014-06-18 22:16:58 +00:00
|
|
|
|
|
|
|
func printstack() {
|
|
|
|
stack := strings.Join(strings.Split(string(debug.Stack()), "\n")[2:], "\n")
|
|
|
|
fmt.Fprintln(os.Stderr, stack)
|
|
|
|
}
|