bbolt/freelist.go
Ben Johnson 642b104396 Add DefaultOptions variable.
This commit adds an explicit DefaultOptions variable for additional documentation.
Open() can still be passed a nil options which will cause options to be change to
the DefaultOptions variable. This change also allows options to be set globally for
an application if more than one database is being opened in a process.

This commit also moves all errors to errors.go so that the godoc groups them together.
2014-06-22 12:44:20 -06:00

194 lines
4.6 KiB
Go

package bolt
import (
"fmt"
"sort"
"unsafe"
)
// freelist represents a list of all pages that are available for allocation.
// It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct {
ids []pgid
pending map[txid][]pgid
}
// size returns the size of the page after serialization.
func (f *freelist) size() int {
return pageHeaderSize + (int(unsafe.Sizeof(pgid(0))) * f.count())
}
// count returns count of pages on the freelist
func (f *freelist) count() int {
return f.free_count() + f.pending_count()
}
// free_count returns count of free pages
func (f *freelist) free_count() int {
return len(f.ids)
}
// pending_count returns count of pending pages
func (f *freelist) pending_count() int {
var count int
for _, list := range f.pending {
count += len(list)
}
return count
}
// all returns a list of all free ids and all pending ids in one sorted list.
func (f *freelist) all() []pgid {
ids := make([]pgid, len(f.ids))
copy(ids, f.ids)
for _, list := range f.pending {
ids = append(ids, list...)
}
sort.Sort(pgids(ids))
return ids
}
// allocate returns the starting page id of a contiguous list of pages of a given size.
// If a contiguous block cannot be found then 0 is returned.
func (f *freelist) allocate(n int) pgid {
if len(f.ids) == 0 {
return 0
}
var initial, previd pgid
for i, id := range f.ids {
_assert(id > 1, "invalid page allocation: %d", id)
// Reset initial page if this is not contiguous.
if previd == 0 || id-previd != 1 {
initial = id
}
// If we found a contiguous block then remove it and return it.
if (id-initial)+1 == pgid(n) {
// If we're allocating off the beginning then take the fast path
// and just adjust the existing slice. This will use extra memory
// temporarily but the append() in free() will realloc the slice
// as is necessary.
if (i + 1) == n {
f.ids = f.ids[i+1:]
} else {
copy(f.ids[i-n+1:], f.ids[i+1:])
f.ids = f.ids[:len(f.ids)-n]
}
return initial
}
previd = id
}
return 0
}
// free releases a page and its overflow for a given transaction id.
// If the page is already free then a panic will occur.
func (f *freelist) free(txid txid, p *page) {
_assert(p.id > 1, "cannot free page 0 or 1: %d", p.id)
// Verify that page is not already free.
minid, maxid := p.id, p.id+pgid(p.overflow)
for _, id := range f.ids {
if id >= minid && id <= maxid {
panic(fmt.Sprintf("page %d already freed in tx", id))
}
}
for ptxid, m := range f.pending {
for _, id := range m {
if id >= minid && id <= maxid {
panic(fmt.Sprintf("tx %d: page %d already freed in tx %d", txid, id, ptxid))
}
}
}
// Free page and all its overflow pages.
var ids = f.pending[txid]
for i := 0; i < int(p.overflow+1); i++ {
ids = append(ids, p.id+pgid(i))
}
f.pending[txid] = ids
}
// release moves all page ids for a transaction id (or older) to the freelist.
func (f *freelist) release(txid txid) {
for tid, ids := range f.pending {
if tid <= txid {
f.ids = append(f.ids, ids...)
delete(f.pending, tid)
}
}
sort.Sort(pgids(f.ids))
}
// rollback removes the pages from a given pending tx.
func (f *freelist) rollback(txid txid) {
delete(f.pending, txid)
}
// isFree returns whether a given page is in the free list.
func (f *freelist) isFree(pgid pgid) bool {
for _, id := range f.ids {
if id == pgid {
return true
}
}
for _, m := range f.pending {
for _, id := range m {
if id == pgid {
return true
}
}
}
return false
}
// read initializes the freelist from a freelist page.
func (f *freelist) read(p *page) {
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count]
f.ids = make([]pgid, len(ids))
copy(f.ids, ids)
sort.Sort(pgids(f.ids))
}
// write writes the page ids onto a freelist page. All free and pending ids are
// saved to disk since in the event of a program crash, all pending ids will
// become free.
func (f *freelist) write(p *page) error {
// Combine the old free pgids and pgids waiting on an open transaction.
ids := f.all()
// Make sure that the sum of all free pages is less than the max uint16 size.
if len(ids) >= 65565 {
return ErrFreelistOverflow
}
// Update the header and write the ids to the page.
p.flags |= freelistPageFlag
p.count = uint16(len(ids))
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids)
return nil
}
// reload reads the freelist from a page and filters out pending items.
func (f *freelist) reload(p *page) {
f.read(p)
// Filter out pending free pages.
ids := f.ids
f.ids = nil
var a []pgid
for _, id := range ids {
if !f.isFree(id) {
a = append(a, id)
}
}
f.ids = a
}