Source File
db.go
Belonging Package
go.etcd.io/bbolt
package bboltimport ()// 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 = 0xED0CDAEDconst 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 = 1000DefaultMaxBatchDelay = 10 * time.MillisecondDefaultAllocSize = 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 backendtype FreelistType stringconst (// FreelistArrayType indicates backend freelist type is arrayFreelistArrayType = FreelistType("array")// FreelistMapType indicates backend freelist type is hashmapFreelistMapType = FreelistType("hashmap"))// DB represents a collection of buckets persisted to a file on disk.// 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.type DB struct {// 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// 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.//// If the package global IgnoreNoSync constant is true, this value is// ignored. See the comment on that constant for more details.//// THIS IS UNSAFE. PLEASE USE WITH CAUTION.NoSync bool// When true, skips syncing freelist to disk. This improves the database// write performance under normal operation, but requires a full database// re-sync during recovery.NoFreelistSync bool// FreelistType sets the backend freelist type. There are two options. Array which is simple but endures// dramatic performance degradation if database is large and framentation in freelist is common.// The alternative one is using hashmap, it is faster in almost all circumstances// but it doesn't guarantee that it offers the smallest page id available. In normal case it is safe.// The default type is arrayFreelistType FreelistType// When true, skips the truncate call when growing the database.// Setting this to true is only safe on non-ext3/ext4 systems.// Skipping truncation avoids preallocation of hard drive space and// bypasses a truncate() and fsync() syscall on remapping.//// https://github.com/boltdb/bolt/issues/284NoGrowSync bool// If you want to read the entire database fast, you can set MmapFlag to// syscall.MAP_POPULATE on Linux 2.6.23+ for sequential read-ahead.MmapFlags int// MaxBatchSize is the maximum size of a batch. Default value is// copied from DefaultMaxBatchSize in Open.//// If <=0, disables batching.//// Do not change concurrently with calls to Batch.MaxBatchSize int// MaxBatchDelay is the maximum delay before a batch starts.// Default value is copied from DefaultMaxBatchDelay in Open.//// If <=0, effectively disables batching.//// Do not change concurrently with calls to Batch.MaxBatchDelay time.Duration// AllocSize is the amount of space allocated when the database// needs to create new pages. This is done to amortize the cost// of truncate() and fsync() when growing the data file.AllocSize intpath stringopenFile func(string, int, os.FileMode) (*os.File, error)file *os.Filedataref []byte // mmap'ed readonly, write throws SEGVdata *[maxMapSize]bytedatasz intfilesz int // current on disk file sizemeta0 *metameta1 *metapageSize intopened boolrwtx *Txtxs []*Txstats Statsfreelist *freelistfreelistLoad sync.OncepagePool sync.PoolbatchMu sync.Mutexbatch *batchrwlock sync.Mutex // Allows only one writer at a time.metalock sync.Mutex // Protects meta page access.mmaplock sync.RWMutex // Protects mmap access during remapping.statlock sync.RWMutex // Protects stats access.ops struct {writeAt func(b []byte, off int64) (n int, err error)}// Read only mode.// When true, Update() and Begin(true) return ErrDatabaseReadOnly immediately.readOnly bool}// Path returns the path to currently open database file.func ( *DB) () string {return .path}// GoString returns the Go string representation of the database.func ( *DB) () string {return fmt.Sprintf("bolt.DB{path:%q}", .path)}// String returns the string representation of the database.func ( *DB) () string {return fmt.Sprintf("DB<%q>", .path)}// Open creates and opens a database at the given path.// If the file does not exist then it will be created automatically.// Passing in nil options will cause Bolt to open the database with the default options.func ( string, os.FileMode, *Options) (*DB, error) {:= &DB{opened: true,}// Set default options if no options are provided.if == nil {= DefaultOptions}.NoSync = .NoSync.NoGrowSync = .NoGrowSync.MmapFlags = .MmapFlags.NoFreelistSync = .NoFreelistSync.FreelistType = .FreelistType// Set default values for later DB operations..MaxBatchSize = DefaultMaxBatchSize.MaxBatchDelay = DefaultMaxBatchDelay.AllocSize = DefaultAllocSize:= os.O_RDWRif .ReadOnly {= os.O_RDONLY.readOnly = true}.openFile = .OpenFileif .openFile == nil {.openFile = os.OpenFile}// Open data file and separate sync handler for metadata writes.var errorif .file, = .openFile(, |os.O_CREATE, ); != nil {_ = .close()return nil,}.path = .file.Name()// 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 := flock(, !.readOnly, .Timeout); != nil {_ = .close()return nil,}// Default values for test hooks.ops.writeAt = .file.WriteAtif .pageSize = .PageSize; .pageSize == 0 {// Set the default page size to the OS page size..pageSize = defaultPageSize}// Initialize the database if it doesn't exist.if , := .file.Stat(); != nil {_ = .close()return nil,} else if .Size() == 0 {// Initialize new files with meta pages.if := .init(); != nil {// clean up file descriptor on initialization fail_ = .close()return nil,}} else {// Read the first meta page to determine the page size.var [0x1000]byte// If we can't read the page size, but can read a page, assume// it's the same as the OS or one given -- since that's how the// page size was chosen in the first place.//// If the first page is invalid and this OS uses a different// page size than what the database was created with then we// are out of luck and cannot access the database.//// TODO: scan for next pageif , := .file.ReadAt([:], 0); == nil && == len() {if := .pageInBuffer([:], 0).meta(); .validate() == nil {.pageSize = int(.pageSize)}} else {_ = .close()return nil, ErrInvalid}}// Initialize page pool..pagePool = sync.Pool{New: func() interface{} {return make([]byte, .pageSize)},}// Memory map the data file.if := .mmap(.InitialMmapSize); != nil {_ = .close()return nil,}if .readOnly {return , nil}.loadFreelist()// Flush freelist when transitioning from no sync to sync so// NoFreelistSync unaware boltdb can open the db later.if !.NoFreelistSync && !.hasSyncedFreelist() {, := .Begin(true)if != nil {= .Commit()}if != nil {_ = .close()return nil,}}// Mark the database as opened and return.return , nil}// loadFreelist reads the freelist if it is synced, or reconstructs it// by scanning the DB if it is not synced. It assumes there are no// concurrent accesses being made to the freelist.func ( *DB) () {.freelistLoad.Do(func() {.freelist = newFreelist(.FreelistType)if !.hasSyncedFreelist() {// Reconstruct free list by scanning the DB..freelist.readIDs(.freepages())} else {// Read free list from freelist page..freelist.read(.page(.meta().freelist))}.stats.FreePageN = .freelist.free_count()})}func ( *DB) () bool {return .meta().freelist != pgidNoFreelist}// mmap opens the underlying memory-mapped file and initializes the meta references.// minsz is the minimum size that the new mmap can be.func ( *DB) ( int) error {.mmaplock.Lock()defer .mmaplock.Unlock(), := .file.Stat()if != nil {return fmt.Errorf("mmap stat error: %s", )} else if int(.Size()) < .pageSize*2 {return fmt.Errorf("file size too small")}// Ensure the size is at least the minimum size.var = int(.Size())if < {=}, = .mmapSize()if != nil {return}// Dereference all mmap references before unmapping.if .rwtx != nil {.rwtx.root.dereference()}// Unmap existing data before continuing.if := .munmap(); != nil {return}// Memory-map the data file as a byte slice.if := mmap(, ); != nil {return}// Save references to the meta pages..meta0 = .page(0).meta().meta1 = .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.:= .meta0.validate():= .meta1.validate()if != nil && != nil {return}return nil}// munmap unmaps the data file from memory.func ( *DB) () error {if := munmap(); != nil {return fmt.Errorf("unmap error: " + .Error())}return nil}// mmapSize determines the appropriate size for the mmap given the current size// of the database. The minimum size is 32KB and doubles until it reaches 1GB.// Returns an error if the new mmap size is greater than the max allowed.func ( *DB) ( int) (int, error) {// Double the size from 32KB until 1GB.for := uint(15); <= 30; ++ {if <= 1<< {return 1 << , nil}}// Verify the requested size is not above the maximum allowed.if > maxMapSize {return 0, fmt.Errorf("mmap too large")}// If larger than 1GB then grow by 1GB at a time.:= int64()if := % int64(maxMmapStep); > 0 {+= int64(maxMmapStep) -}// Ensure that the mmap size is a multiple of the page size.// This should always be true since we're incrementing in MBs.:= int64(.pageSize)if ( % ) != 0 {= (( / ) + 1) *}// If we've exceeded the max size then only grow up to the max size.if > maxMapSize {= maxMapSize}return int(), nil}// init creates a new database file and initializes its meta pages.func ( *DB) () error {// Create two meta pages on a buffer.:= make([]byte, .pageSize*4)for := 0; < 2; ++ {:= .pageInBuffer([:], pgid()).id = pgid().flags = metaPageFlag// Initialize the meta page.:= .meta().magic = magic.version = version.pageSize = uint32(.pageSize).freelist = 2.root = bucket{root: 3}.pgid = 4.txid = txid().checksum = .sum64()}// Write an empty freelist at page 3.:= .pageInBuffer([:], pgid(2)).id = pgid(2).flags = freelistPageFlag.count = 0// Write an empty leaf page at page 4.= .pageInBuffer([:], pgid(3)).id = pgid(3).flags = leafPageFlag.count = 0// Write the buffer to our data file.if , := .ops.writeAt(, 0); != nil {return}if := fdatasync(); != nil {return}return nil}// Close releases all database resources.// It will block waiting for any open transactions to finish// before closing the database and returning.func ( *DB) () error {.rwlock.Lock()defer .rwlock.Unlock().metalock.Lock()defer .metalock.Unlock().mmaplock.Lock()defer .mmaplock.Unlock()return .close()}func ( *DB) () error {if !.opened {return nil}.opened = false.freelist = nil// Clear ops..ops.writeAt = nil// Close the mmap.if := .munmap(); != nil {return}// Close file handles.if .file != nil {// No need to unlock read-only file.if !.readOnly {// Unlock the file.if := funlock(); != nil {log.Printf("bolt.Close(): funlock error: %s", )}}// Close the file descriptor.if := .file.Close(); != nil {return fmt.Errorf("db file close: %s", )}.file = nil}.path = ""return nil}// 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.//// Transactions should not be dependent on one another. Opening a read// transaction and a write transaction in the same goroutine can cause the// writer to deadlock because the database periodically needs to re-mmap itself// as it grows and it cannot do that while a read transaction is open.//// If a long running read transaction (for example, a snapshot transaction) is// needed, you might want to set DB.InitialMmapSize to a large enough value// to avoid potential blocking of write transaction.//// IMPORTANT: You must close read-only transactions after you are finished or// else the database will not reclaim old pages.func ( *DB) ( bool) (*Tx, error) {if {return .beginRWTx()}return .beginTx()}func ( *DB) () (*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// write transaction will obtain them..metalock.Lock()// 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..mmaplock.RLock()// Exit if the database is not open yet.if !.opened {.mmaplock.RUnlock().metalock.Unlock()return nil, ErrDatabaseNotOpen}// Create a transaction associated with the database.:= &Tx{}.init()// Keep track of transaction until it closes..txs = append(.txs, ):= len(.txs)// Unlock the meta pages..metalock.Unlock()// Update the transaction stats..statlock.Lock().stats.TxN++.stats.OpenTxN =.statlock.Unlock()return , nil}func ( *DB) () (*Tx, error) {// If the database was opened with Options.ReadOnly, return an error.if .readOnly {return nil, ErrDatabaseReadOnly}// Obtain writer lock. This is released by the transaction when it closes.// This enforces only one writer transaction at a time..rwlock.Lock()// Once we have the writer lock then we can lock the meta pages so that// we can set up the transaction..metalock.Lock()defer .metalock.Unlock()// Exit if the database is not open yet.if !.opened {.rwlock.Unlock()return nil, ErrDatabaseNotOpen}// Create a transaction associated with the database.:= &Tx{writable: true}.init().rwtx =.freePages()return , nil}// freePages releases any pages associated with closed read-only transactions.func ( *DB) () {// Free all pending pages prior to earliest open transaction.sort.Sort(txsById(.txs)):= txid(0xFFFFFFFFFFFFFFFF)if len(.txs) > 0 {= .txs[0].meta.txid}if > 0 {.freelist.release( - 1)}// Release unused txid extents.for , := range .txs {.freelist.releaseRange(, .meta.txid-1)= .meta.txid + 1}.freelist.releaseRange(, txid(0xFFFFFFFFFFFFFFFF))// Any page both allocated and freed in an extent is safe to release.}type txsById []*Txfunc ( txsById) () int { return len() }func ( txsById) (, int) { [], [] = [], [] }func ( txsById) (, int) bool { return [].meta.txid < [].meta.txid }// removeTx removes a transaction from the database.func ( *DB) ( *Tx) {// Release the read lock on the mmap..mmaplock.RUnlock()// Use the meta lock to restrict access to the DB object..metalock.Lock()// Remove the transaction.for , := range .txs {if == {:= len(.txs) - 1.txs[] = .txs[].txs[] = nil.txs = .txs[:]break}}:= len(.txs)// Unlock the meta pages..metalock.Unlock()// Merge statistics..statlock.Lock().stats.OpenTxN =.stats.TxStats.add(&.stats).statlock.Unlock()}// Update executes a function within the context of a read-write managed transaction.// 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// returned from the Update() method.//// Attempting to manually commit or rollback within the function will cause a panic.func ( *DB) ( func(*Tx) error) error {, := .Begin(true)if != nil {return}// Make sure the transaction rolls back in the event of a panic.defer func() {if .db != nil {.rollback()}}()// Mark as a managed tx so that the inner function cannot manually commit..managed = true// If an error is returned from the function then rollback and return error.= ().managed = falseif != nil {_ = .Rollback()return}return .Commit()}// 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.//// Attempting to manually rollback within the function will cause a panic.func ( *DB) ( func(*Tx) error) error {, := .Begin(false)if != nil {return}// Make sure the transaction rolls back in the event of a panic.defer func() {if .db != nil {.rollback()}}()// Mark as a managed tx so that the inner function cannot manually rollback..managed = true// If an error is returned from the function then pass it through.= ().managed = falseif != nil {_ = .Rollback()return}return .Rollback()}// Batch calls fn as part of a batch. It behaves similar to Update,// except://// 1. concurrent Batch calls can be combined into a single Bolt// transaction.//// 2. the function passed to Batch may be called multiple times,// regardless of whether it returns error or not.//// This means that Batch function side effects must be idempotent and// take permanent effect only after a successful return is seen in// caller.//// The maximum batch size and delay can be adjusted with DB.MaxBatchSize// and DB.MaxBatchDelay, respectively.//// Batch is only useful when there are multiple goroutines calling it.func ( *DB) ( func(*Tx) error) error {:= make(chan error, 1).batchMu.Lock()if (.batch == nil) || (.batch != nil && len(.batch.calls) >= .MaxBatchSize) {// There is no existing batch, or the existing batch is full; start a new one..batch = &batch{db: ,}.batch.timer = time.AfterFunc(.MaxBatchDelay, .batch.trigger)}.batch.calls = append(.batch.calls, call{fn: , err: })if len(.batch.calls) >= .MaxBatchSize {// wake up batch, it's ready to rungo .batch.trigger()}.batchMu.Unlock():= <-if == trySolo {= .Update()}return}type call struct {fn func(*Tx) errorerr chan<- error}type batch struct {db *DBtimer *time.Timerstart sync.Oncecalls []call}// trigger runs the batch if it hasn't already been run.func ( *batch) () {.start.Do(.run)}// run performs the transactions in the batch and communicates results// back to DB.Batch.func ( *batch) () {.db.batchMu.Lock().timer.Stop()// Make sure no new work is added to this batch, but don't break// other batches.if .db.batch == {.db.batch = nil}.db.batchMu.Unlock():for len(.calls) > 0 {var = -1:= .db.Update(func( *Tx) error {for , := range .calls {if := safelyCall(.fn, ); != nil {=return}}return nil})if >= 0 {// take the failing transaction out of the batch. it's// safe to shorten b.calls here because db.batch no longer// points to us, and we hold the mutex anyway.:= .calls[].calls[], .calls = .calls[len(.calls)-1], .calls[:len(.calls)-1]// tell the submitter re-run it solo, continue with the rest of the batch.err <- trySolocontinue}// pass success, or bolt internal errors, to all callersfor , := range .calls {.err <-}break}}// trySolo is a special sentinel error value used for signaling that a// transaction function should be re-run. It should never be seen by// callers.var trySolo = errors.New("batch function returned an error and should be re-run solo")type panicked struct {reason interface{}}func ( panicked) () string {if , := .reason.(error); {return .Error()}return fmt.Sprintf("panic: %v", .reason)}func ( func(*Tx) error, *Tx) ( error) {defer func() {if := recover(); != nil {= panicked{}}}()return ()}// Sync executes fdatasync() against the database file handle.//// 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) () error { return fdatasync() }// Stats retrieves ongoing performance stats for the database.// This is only updated when a transaction closes.func ( *DB) () Stats {.statlock.RLock()defer .statlock.RUnlock()return .stats}// This is for internal access to the raw data bytes from the C cursor, use// carefully, or not at all.func ( *DB) () *Info {return &Info{uintptr(unsafe.Pointer(&.data[0])), .pageSize}}// page retrieves a page reference from the mmap based on the current page size.func ( *DB) ( pgid) *page {:= * pgid(.pageSize)return (*page)(unsafe.Pointer(&.data[]))}// pageInBuffer retrieves a page reference from a given byte array based on the current page size.func ( *DB) ( []byte, pgid) *page {return (*page)(unsafe.Pointer(&[*pgid(.pageSize)]))}// meta retrieves the current meta page reference.func ( *DB) () *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.:= .meta0:= .meta1if .meta1.txid > .meta0.txid {= .meta1= .meta0}// Use higher meta page if valid. Otherwise fallback to previous, if valid.if := .validate(); == nil {return} else if := .validate(); == nil {return}// This should never be reached, because both meta1 and meta0 were validated// on mmap() and we do fsync() on every write.panic("bolt.DB.meta(): invalid meta pages")}// allocate returns a contiguous block of memory starting at a given page.func ( *DB) ( txid, int) (*page, error) {// Allocate a temporary buffer for the page.var []byteif == 1 {= .pagePool.Get().([]byte)} else {= make([]byte, *.pageSize)}:= (*page)(unsafe.Pointer(&[0])).overflow = uint32( - 1)// Use pages from the freelist if they are available.if .id = .freelist.allocate(, ); .id != 0 {return , nil}// Resize mmap() if we're at the end..id = .rwtx.meta.pgidvar = int((.id+pgid())+1) * .pageSizeif >= .datasz {if := .mmap(); != nil {return nil, fmt.Errorf("mmap allocate error: %s", )}}// Move the page id high water mark..rwtx.meta.pgid += pgid()return , nil}// grow grows the size of the database to the given sz.func ( *DB) ( int) error {// Ignore if the new size is less than available file size.if <= .filesz {return nil}// If the data is smaller than the alloc size then only allocate what's needed.// Once it goes over the allocation size then allocate in chunks.if .datasz < .AllocSize {= .datasz} else {+= .AllocSize}// Truncate and fsync to ensure file size metadata is flushed.// https://github.com/boltdb/bolt/issues/284if !.NoGrowSync && !.readOnly {if runtime.GOOS != "windows" {if := .file.Truncate(int64()); != nil {return fmt.Errorf("file resize error: %s", )}}if := .file.Sync(); != nil {return fmt.Errorf("file sync error: %s", )}}.filesz =return nil}func ( *DB) () bool {return .readOnly}func ( *DB) () []pgid {, := .beginTx()defer func() {= .Rollback()if != nil {panic("freepages: failed to rollback tx")}}()if != nil {panic("freepages: failed to open read only tx")}:= make(map[pgid]*page):= make(map[pgid]bool):= make(chan error)go func() {for := range {panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", ))}}().checkBucket(&.root, , , )close()var []pgidfor := pgid(2); < .meta().pgid; ++ {if , := []; ! {= append(, )}}return}// 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// Sets the DB.NoGrowSync flag before memory mapping the file.NoGrowSync bool// Do not sync freelist to disk. This improves the database write performance// under normal operation, but requires a full database re-sync during recovery.NoFreelistSync bool// FreelistType sets the backend freelist type. There are two options. Array which is simple but endures// dramatic performance degradation if database is large and framentation in freelist is common.// The alternative one is using hashmap, it is faster in almost all circumstances// but it doesn't guarantee that it offers the smallest page id available. In normal case it is safe.// The default type is arrayFreelistType FreelistType// Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to// grab a shared lock (UNIX).ReadOnly bool// Sets the DB.MmapFlags flag before memory mapping the file.MmapFlags int// InitialMmapSize is the initial mmap size of the database// in bytes. Read transactions won't block write transaction// if the InitialMmapSize is large enough to hold database mmap// size. (See DB.Begin for more information)//// If <=0, the initial map size is 0.// If initialMmapSize is smaller than the previous database size,// it takes no effect.InitialMmapSize int// PageSize overrides the default OS page size.PageSize int// NoSync sets the initial value of DB.NoSync. Normally this can just be// set directly on the DB itself when returned from Open(), but this option// is useful in APIs which expose Options but not the underlying DB.NoSync bool// OpenFile is used to open files. It defaults to os.OpenFile. This option// is useful for writing hermetic tests.OpenFile func(string, int, os.FileMode) (*os.File, error)}// 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,NoGrowSync: false,FreelistType: FreelistArrayType,}// Stats represents statistics about the database.type Stats struct {// Freelist statsFreePageN int // total number of free pages on the freelistPendingPageN int // total number of pending pages on the freelistFreeAlloc int // total bytes allocated in free pagesFreelistInuse int // total bytes used by the freelist// Transaction statsTxN int // total number of started read transactionsOpenTxN int // number of currently open read transactionsTxStats TxStats // global, ongoing stats.}// 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 ( *Stats) ( *Stats) Stats {if == nil {return *}var Stats.FreePageN = .FreePageN.PendingPageN = .PendingPageN.FreeAlloc = .FreeAlloc.FreelistInuse = .FreelistInuse.TxN = .TxN - .TxN.TxStats = .TxStats.Sub(&.TxStats)return}type Info struct {Data uintptrPageSize int}type meta struct {magic uint32version uint32pageSize uint32flags uint32root bucketfreelist pgidpgid pgidtxid txidchecksum uint64}// validate checks the marker bytes and version of the meta page to ensure it matches this binary.func ( *meta) () error {if .magic != magic {return ErrInvalid} else if .version != version {return ErrVersionMismatch} else if .checksum != 0 && .checksum != .sum64() {return ErrChecksum}return nil}// copy copies one meta object to another.func ( *meta) ( *meta) {* = *}// write writes the meta onto a page.func ( *meta) ( *page) {if .root.root >= .pgid {panic(fmt.Sprintf("root bucket pgid (%d) above high water mark (%d)", .root.root, .pgid))} else if .freelist >= .pgid && .freelist != pgidNoFreelist {// TODO: reject pgidNoFreeList if !NoFreelistSyncpanic(fmt.Sprintf("freelist pgid (%d) above high water mark (%d)", .freelist, .pgid))}// Page id is either going to be 0 or 1 which we can determine by the transaction ID..id = pgid(.txid % 2).flags |= metaPageFlag// Calculate the checksum..checksum = .sum64().copy(.meta())}// generates the checksum for the meta.func ( *meta) () uint64 {var = fnv.New64a()_, _ = .Write((*[unsafe.Offsetof(meta{}.checksum)]byte)(unsafe.Pointer())[:])return .Sum64()}// _assert will panic with a given formatted message if the given condition is false.func ( bool, string, ...interface{}) {if ! {panic(fmt.Sprintf("assertion failed: "+, ...))}}
The pages are generated with Golds v0.2.8-preview. (GOOS=darwin GOARCH=arm64)