package bboltimport ()// txPending holds a list of pgids and corresponding allocation txns// that are pending to be freed.type txPending struct { ids []pgid alloctx []txid// txids allocating the ids lastReleaseBegin txid// beginning txid of last matching releaseRange}// pidSet holds the set of starting pgids which have the same span sizetype pidSet map[pgid]struct{}// 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 { freelistType FreelistType// freelist type ids []pgid// all free and available free page ids. allocs map[pgid]txid// mapping of txid that allocated a pgid. pending map[txid]*txPending// mapping of soon-to-be free page ids by tx. cache map[pgid]bool// fast lookup of all free and pending page ids. freemaps map[uint64]pidSet// key is the size of continuous pages(span), value is a set which contains the starting pgids of same size forwardMap map[pgid]uint64// key is start pgid, value is its span size backwardMap map[pgid]uint64// key is end pgid, value is its span size allocate func(txid txid, n int) pgid// the freelist allocate func free_count func() int// the function which gives you free page number mergeSpans func(ids pgids) // the mergeSpan func getFreePageIDs func() []pgid// get free pgids func readIDs func(pgids []pgid) // readIDs func reads list of pages and init the freelist}// newFreelist returns an empty, initialized freelist.func newFreelist( FreelistType) *freelist { := &freelist{freelistType: ,allocs: make(map[pgid]txid),pending: make(map[txid]*txPending),cache: make(map[pgid]bool),freemaps: make(map[uint64]pidSet),forwardMap: make(map[pgid]uint64),backwardMap: make(map[pgid]uint64), }if == FreelistMapType { .allocate = .hashmapAllocate .free_count = .hashmapFreeCount .mergeSpans = .hashmapMergeSpans .getFreePageIDs = .hashmapGetFreePageIDs .readIDs = .hashmapReadIDs } else { .allocate = .arrayAllocate .free_count = .arrayFreeCount .mergeSpans = .arrayMergeSpans .getFreePageIDs = .arrayGetFreePageIDs .readIDs = .arrayReadIDs }return}// size returns the size of the page after serialization.func ( *freelist) () int { := .count()if >= 0xFFFF {// The first element will be used to store the count. See freelist.write. ++ }returnint(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * )}// count returns count of pages on the freelistfunc ( *freelist) () int {return .free_count() + .pending_count()}// arrayFreeCount returns count of free pages(array version)func ( *freelist) () int {returnlen(.ids)}// pending_count returns count of pending pagesfunc ( *freelist) () int {varintfor , := range .pending { += len(.ids) }return}// copyall copies a list of all free ids and all pending ids in one sorted list.// f.count returns the minimum length required for dst.func ( *freelist) ( []pgid) { := make(pgids, 0, .pending_count())for , := range .pending { = append(, .ids...) }sort.Sort()mergepgids(, .getFreePageIDs(), )}// arrayAllocate 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 ( *freelist) ( txid, int) pgid {iflen(.ids) == 0 {return0 }var , pgidfor , := range .ids {if <= 1 {panic(fmt.Sprintf("invalid page allocation: %d", )) }// Reset initial page if this is not contiguous.if == 0 || - != 1 { = }// If we found a contiguous block then remove it and return it.if (-)+1 == pgid() {// 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 ( + 1) == { .ids = .ids[+1:] } else {copy(.ids[-+1:], .ids[+1:]) .ids = .ids[:len(.ids)-] }// Remove from the free cache.for := pgid(0); < pgid(); ++ {delete(.cache, +) } .allocs[] = return } = }return0}// free releases a page and its overflow for a given transaction id.// If the page is already free then a panic will occur.func ( *freelist) ( txid, *page) {if .id <= 1 {panic(fmt.Sprintf("cannot free page 0 or 1: %d", .id)) }// Free page and all its overflow pages. := .pending[]if == nil { = &txPending{} .pending[] = } , := .allocs[.id]if {delete(.allocs, .id) } elseif (.flags & freelistPageFlag) != 0 {// Freelist is always allocated by prior tx. = - 1 }for := .id; <= .id+pgid(.overflow); ++ {// Verify that page is not already free.if .cache[] {panic(fmt.Sprintf("page %d already freed", )) }// Add to the freelist and cache. .ids = append(.ids, ) .alloctx = append(.alloctx, ) .cache[] = true }}// release moves all page ids for a transaction id (or older) to the freelist.func ( *freelist) ( txid) { := make(pgids, 0)for , := range .pending {if <= {// Move transaction's pending pages to the available freelist. // Don't remove from the cache since the page is still free. = append(, .ids...)delete(.pending, ) } } .mergeSpans()}// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.func ( *freelist) (, txid) {if > {return }varpgidsfor , := range .pending {if < || > {continue }// Don't recompute freed pages if ranges haven't updated.if .lastReleaseBegin == {continue }for := 0; < len(.ids); ++ {if := .alloctx[]; < || > {continue } = append(, .ids[]) .ids[] = .ids[len(.ids)-1] .ids = .ids[:len(.ids)-1] .alloctx[] = .alloctx[len(.alloctx)-1] .alloctx = .alloctx[:len(.alloctx)-1] -- } .lastReleaseBegin = iflen(.ids) == 0 {delete(.pending, ) } } .mergeSpans()}// rollback removes the pages from a given pending tx.func ( *freelist) ( txid) {// Remove page ids from cache. := .pending[]if == nil {return }varpgidsfor , := range .ids {delete(.cache, ) := .alloctx[]if == 0 {continue }if != {// Pending free aborted; restore page back to alloc list. .allocs[] = } else {// Freed page was allocated by this txn; OK to throw away. = append(, ) } }// Remove pages from pending list and mark as free if allocated by txid.delete(.pending, ) .mergeSpans()}// freed returns whether a given page is in the free list.func ( *freelist) ( pgid) bool {return .cache[]}// read initializes the freelist from a freelist page.func ( *freelist) ( *page) {if (.flags & freelistPageFlag) == 0 {panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", .id, .typ())) }// If the page.count is at the max uint16 value (64k) then it's considered // an overflow and the size of the freelist is stored as the first element.var , = 0, int(.count)if == 0xFFFF { = 1 := *(*pgid)(unsafeAdd(unsafe.Pointer(), unsafe.Sizeof(*))) = int()if < 0 {panic(fmt.Sprintf("leading element count %d overflows int", )) } }// Copy the list of page ids from the freelist.if == 0 { .ids = nil } else {var []pgid := unsafeIndex(unsafe.Pointer(), unsafe.Sizeof(*), unsafe.Sizeof([0]), )unsafeSlice(unsafe.Pointer(&), , )// copy the ids, so we don't modify on the freelist page directly := make([]pgid, )copy(, )// Make sure they're sorted.sort.Sort(pgids()) .readIDs() }}// arrayReadIDs initializes the freelist from a given list of ids.func ( *freelist) ( []pgid) { .ids = .reindex()}func ( *freelist) () []pgid {return .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 ( *freelist) ( *page) error {// Combine the old free pgids and pgids waiting on an open transaction.// Update the header flag. .flags |= freelistPageFlag// The page.count can only hold up to 64k elements so if we overflow that // number then we handle it by putting the size in the first element. := .count()if == 0 { .count = uint16() } elseif < 0xFFFF { .count = uint16()var []pgid := unsafeAdd(unsafe.Pointer(), unsafe.Sizeof(*))unsafeSlice(unsafe.Pointer(&), , ) .copyall() } else { .count = 0xFFFFvar []pgid := unsafeAdd(unsafe.Pointer(), unsafe.Sizeof(*))unsafeSlice(unsafe.Pointer(&), , +1) [0] = pgid() .copyall([1:]) }returnnil}// reload reads the freelist from a page and filters out pending items.func ( *freelist) ( *page) { .read()// Build a cache of only pending pages. := make(map[pgid]bool)for , := range .pending {for , := range .ids { [] = true } }// Check each page in the freelist and build a new available freelist // with any pages not in the pending lists.var []pgidfor , := range .getFreePageIDs() {if ![] { = append(, ) } } .readIDs()}// noSyncReload reads the freelist from pgids and filters out pending items.func ( *freelist) ( []pgid) {// Build a cache of only pending pages. := make(map[pgid]bool)for , := range .pending {for , := range .ids { [] = true } }// Check each page in the freelist and build a new available freelist // with any pages not in the pending lists.var []pgidfor , := range {if ![] { = append(, ) } } .readIDs()}// reindex rebuilds the free cache based on available and pending free lists.func ( *freelist) () { := .getFreePageIDs() .cache = make(map[pgid]bool, len())for , := range { .cache[] = true }for , := range .pending {for , := range .ids { .cache[] = true } }}// arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using arrayfunc ( *freelist) ( pgids) {sort.Sort() .ids = pgids(.ids).merge()}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.