diff --git a/Makefile b/Makefile index 61ab8f03b..6a4792795 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ default: build race: @go test -v -race -test.run="TestSimulate_(100op|1000op)" + @echo "array freelist test" + @ARRAY_FREELIST=y go test -v -race -test.run="TestSimulate_(100op|1000op)" fmt: !(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]') @@ -27,4 +29,10 @@ test: # Note: gets "program not an importable package" in out of path builds go test -v ./cmd/bbolt + @echo "array freelist test" + + @ARRAY_FREELIST=y go test -timeout 20m -v -coverprofile cover.out -covermode atomic + # Note: gets "program not an importable package" in out of path builds + @ARRAY_FREELIST=y go test -v ./cmd/bbolt + .PHONY: race fmt errcheck test gosimple unused diff --git a/allocate_test.go b/allocate_test.go index d812c5b68..821b663f8 100644 --- a/allocate_test.go +++ b/allocate_test.go @@ -6,8 +6,12 @@ import ( func TestTx_allocatePageStats(t *testing.T) { f := newFreelist() - ids := []pgid{2, 3} - f.init(ids) + if !f.IsFreelistTypeArray { + ids := []pgid{2, 3} + f.init(ids) + } else { + f.ids = []pgid{2, 3} + } tx := &Tx{ db: &DB{ diff --git a/db.go b/db.go index a72fad5c8..6965e656f 100644 --- a/db.go +++ b/db.go @@ -70,6 +70,13 @@ type DB struct { // re-sync during recovery. NoFreelistSync bool + // IsFreelistTypeArray sets the backend freelist type to array which is simple but endures + // dramatic performance degradation if database is large and framentation in freelist is common. + // The alternative one is using segregated freelist, 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. + // if you depend on this(it offers the smallest page id available), you may set this to true. + IsFreelistTypeArray bool + // 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 @@ -169,6 +176,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { db.NoGrowSync = options.NoGrowSync db.MmapFlags = options.MmapFlags db.NoFreelistSync = options.NoFreelistSync + db.IsFreelistTypeArray = options.IsFreelistTypeArray // Set default values for later DB operations. db.MaxBatchSize = DefaultMaxBatchSize @@ -283,6 +291,11 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { // concurrent accesses being made to the freelist. func (db *DB) loadFreelist() { db.freelistLoad.Do(func() { + if db.IsFreelistTypeArray { + isFreelistTypeArray = true + } else { + isFreelistTypeArray = false + } db.freelist = newFreelist() if !db.hasSyncedFreelist() { // Reconstruct free list by scanning the DB. @@ -1005,6 +1018,13 @@ type Options struct { // under normal operation, but requires a full database re-sync during recovery. NoFreelistSync bool + // IsFreelistTypeArray sets the backend freelist type to array which is simple but endures + // dramatic performance degradation if database is large and framentation in freelist is common. + // The alternative one is using segregated freelist, 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. + // if you depend on this(it offers the smallest page id available), you may set this to true. + IsFreelistTypeArray bool + // Open database in read-only mode. Uses flock(..., LOCK_SH |LOCK_NB) to // grab a shared lock (UNIX). ReadOnly bool diff --git a/freelist.go b/freelist.go index d09cc448b..484aaaa2b 100644 --- a/freelist.go +++ b/freelist.go @@ -2,10 +2,15 @@ package bbolt import ( "fmt" + "os" "sort" "unsafe" ) +const arrayFreelist = "ARRAY_FREELIST" + +var isFreelistTypeArray = false + // txPending holds a list of pgids and corresponding allocation txns // that are pending to be freed. type txPending struct { @@ -20,59 +25,31 @@ type 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 { - 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 + IsFreelistTypeArray bool // 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 } // newFreelist returns an empty, initialized freelist. func newFreelist() *freelist { - return &freelist{ - 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), - } -} - -// initial from pgids -// pgids must be sorted -func (f *freelist) init(pgids []pgid) { - if len(pgids) == 0 { - return - } - - size := uint64(1) - start := pgids[0] - - if !sort.SliceIsSorted([]pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) { - panic("pgids not sorted") + // get it from env for testing + if env := os.Getenv(arrayFreelist); env != "" { + isFreelistTypeArray = true } - - f.freemaps = make(map[uint64]pidSet) - f.forwardMap = make(map[pgid]uint64) - f.backwardMap = make(map[pgid]uint64) - - for i := 1; i < len(pgids); i++ { - // continuous page - if pgids[i] == pgids[i-1]+1 { - size++ - } else { - f.addSpan(start, size) - - size = 1 - start = pgids[i] - } - } - - // init the tail - if size != 0 && start != 0 { - f.addSpan(start, size) + return &freelist{ + IsFreelistTypeArray: isFreelistTypeArray, + ids: make([]pgid, 0), + 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), } } @@ -93,12 +70,11 @@ func (f *freelist) count() int { // free_count returns count of free pages func (f *freelist) free_count() int { - //use the forwardmap to get the total count - count := 0 - for _, size := range f.forwardMap { - count += int(size) + // use the forwardmap to get the total count + if !f.IsFreelistTypeArray { + return f.seglistFree_count() } - return count + return len(f.ids) } // pending_count returns count of pending pages @@ -118,68 +94,56 @@ func (f *freelist) copyall(dst []pgid) { m = append(m, txp.ids...) } sort.Sort(m) - mergepgids(dst, f.getFreePageIDs(), m) -} - -// getFreePageIDs returns the sorted free page ids -func (f *freelist) getFreePageIDs() []pgid { - m := make([]pgid, 0, f.free_count()) - for start, size := range f.forwardMap { - for i := 0; i < int(size); i++ { - m = append(m, start+pgid(i)) - } - } - sort.Sort(pgids(m)) - return m + mergepgids(dst, f.getFreePageIDs(), m) } // 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(txid txid, n int) pgid { - if n == 0 { - return 0 - } - - // if we have a exact size match just return short path - if bm, ok := f.freemaps[uint64(n)]; ok { - for pid := range bm { - // remove the span - f.delSpan(pid, uint64(n)) + if !f.IsFreelistTypeArray { - f.allocs[pid] = txid + return f.seglistAllocate(txid, n) + } - for i := pgid(0); i < pgid(n); i++ { - delete(f.cache, pid+pgid(i)) - } - return pid - } + if len(f.ids) == 0 { + return 0 } - // lookup the map to find larger span - for size, bm := range f.freemaps { - if size < uint64(n) { - continue + var initial, previd pgid + for i, id := range f.ids { + if id <= 1 { + panic(fmt.Sprintf("invalid page allocation: %d", id)) } - for pid := range bm { - // remove the initial - f.delSpan(pid, uint64(size)) - - f.allocs[pid] = txid - - remain := size - uint64(n) + // Reset initial page if this is not contiguous. + if previd == 0 || id-previd != 1 { + initial = id + } - // add remain span - f.addSpan(pid+pgid(n), remain) + // 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] + } + // Remove from the free cache. for i := pgid(0); i < pgid(n); i++ { - delete(f.cache, pid+pgid(i)) + delete(f.cache, initial+i) } - return pid + f.allocs[initial] = txid + return initial } - } + previd = id + } return 0 } @@ -218,6 +182,12 @@ func (f *freelist) free(txid txid, p *page) { // release moves all page ids for a transaction id (or older) to the freelist. func (f *freelist) release(txid txid) { + if !f.IsFreelistTypeArray { + f.seglistRelease(txid) + + return + } + m := make(pgids, 0) for tid, txp := range f.pending { if tid <= txid { @@ -227,67 +197,22 @@ func (f *freelist) release(txid txid) { delete(f.pending, tid) } } - - for _, pgid := range m { - // try to see if we can merge and update - f.mergeWithExistingSpan(pgid) - } -} - -// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward -func (f *freelist) mergeWithExistingSpan(pid pgid) { - prev := pid - 1 - next := pid + 1 - - preSize, mergeWithPrev := f.backwardMap[prev] - nextSize, mergeWithNext := f.forwardMap[next] - newStart := pid - newSize := uint64(1) - - if mergeWithPrev { - //merge with previous span - start := prev + 1 - pgid(preSize) - f.delSpan(start, preSize) - - newStart -= pgid(preSize) - newSize += preSize - } - - if mergeWithNext { - // merge with next span - f.delSpan(next, nextSize) - newSize += nextSize - } - - f.addSpan(newStart, newSize) + sort.Sort(m) + f.ids = pgids(f.ids).merge(m) } -func (f *freelist) addSpan(start pgid, size uint64) { - f.backwardMap[start-1+pgid(size)] = size - f.forwardMap[start] = size - if _, ok := f.freemaps[size]; !ok { - f.freemaps[size] = make(map[pgid]struct{}) - } - - f.freemaps[size][start] = struct{}{} -} +// releaseRange moves pending pages allocated within an extent [begin,end] to the free list. +func (f *freelist) releaseRange(begin, end txid) { + if !f.IsFreelistTypeArray { + f.seglistReleaseRange(begin, end) -func (f *freelist) delSpan(start pgid, size uint64) { - delete(f.forwardMap, start) - delete(f.backwardMap, start+pgid(size-1)) - delete(f.freemaps[size], start) - if len(f.freemaps[size]) == 0 { - delete(f.freemaps, size) + return } -} - -// releaseRange moves pending pages allocated within an extent [begin,end] to the free list. -func (f *freelist) releaseRange(begin, end txid) { if begin > end { return } - var m pgids + m := make(pgids, 0) for tid, txp := range f.pending { if tid < begin || tid > end { continue @@ -312,15 +237,17 @@ func (f *freelist) releaseRange(begin, end txid) { delete(f.pending, tid) } } - - for _, pid := range m { - // try to see if we can merge and update - f.mergeWithExistingSpan(pid) - } + sort.Sort(m) + f.ids = pgids(f.ids).merge(m) } // rollback removes the pages from a given pending tx. func (f *freelist) rollback(txid txid) { + if !f.IsFreelistTypeArray { + f.seglistRollback(txid) + + return + } // Remove page ids from cache. txp := f.pending[txid] if txp == nil { @@ -343,11 +270,8 @@ func (f *freelist) rollback(txid txid) { } // Remove pages from pending list and mark as free if allocated by txid. delete(f.pending, txid) - - for _, pid := range m { - // try to see if we can merge and update - f.mergeWithExistingSpan(pid) - } + sort.Sort(m) + f.ids = pgids(f.ids).merge(m) } // freed returns whether a given page is in the free list. @@ -357,6 +281,11 @@ func (f *freelist) freed(pgid pgid) bool { // read initializes the freelist from a freelist page. func (f *freelist) read(p *page) { + if !f.IsFreelistTypeArray { + f.seglistRead(p) + + return + } if (p.flags & freelistPageFlag) == 0 { panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ())) } @@ -369,9 +298,15 @@ func (f *freelist) read(p *page) { } // Copy the list of page ids from the freelist. - if count != 0 { + if count == 0 { + f.ids = nil + } else { ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx : idx+count] - f.init(ids) + f.ids = make([]pgid, len(ids)) + copy(f.ids, ids) + + // Make sure they're sorted. + sort.Sort(pgids(f.ids)) } // Rebuild the page cache. @@ -380,7 +315,13 @@ func (f *freelist) read(p *page) { // read initializes the freelist from a given list of ids. func (f *freelist) readIDs(ids []pgid) { - f.init(ids) + if !f.IsFreelistTypeArray { + f.seglistReadIDs(ids) + + return + } + + f.ids = ids f.reindex() } @@ -412,6 +353,11 @@ func (f *freelist) write(p *page) error { // reload reads the freelist from a page and filters out pending items. func (f *freelist) reload(p *page) { + if !f.IsFreelistTypeArray { + f.segListReload(p) + + return + } f.read(p) // Build a cache of only pending pages. @@ -425,12 +371,12 @@ func (f *freelist) reload(p *page) { // Check each page in the freelist and build a new available freelist // with any pages not in the pending lists. var a []pgid - for _, id := range f.getFreePageIDs() { + for _, id := range f.ids { if !pcache[id] { a = append(a, id) } } - f.init(a) + f.ids = a // Once the available list is rebuilt then rebuild the free cache so that // it includes the available and pending free pages. @@ -439,11 +385,9 @@ func (f *freelist) reload(p *page) { // reindex rebuilds the free cache based on available and pending free lists. func (f *freelist) reindex() { - f.cache = make(map[pgid]bool, f.free_count()) - for start, size := range f.forwardMap { - for i := 0; i < int(size); i++ { - f.cache[pgid(i)+start] = true - } + f.cache = make(map[pgid]bool, len(f.ids)) + for _, id := range f.ids { + f.cache[id] = true } for _, txp := range f.pending { for _, pendingID := range txp.ids { diff --git a/freelist_test.go b/freelist_test.go index 0b809b626..9b89eec74 100644 --- a/freelist_test.go +++ b/freelist_test.go @@ -155,7 +155,12 @@ func TestFreelist_releaseRange(t *testing.T) { ids = append(ids, pgid(uint64(p.id)+i)) } } - f.init(ids) + if f.IsFreelistTypeArray { + f.ids = ids + } else { + f.init(ids) + } + for _, p := range c.pagesIn { f.allocate(p.allocTxn, p.n) } @@ -178,7 +183,11 @@ func TestFreelist_releaseRange(t *testing.T) { func TestFreelist_allocate(t *testing.T) { f := newFreelist() ids := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18} - f.init(ids) + if f.IsFreelistTypeArray { + f.ids = ids + } else { + f.init(ids) + } f.allocate(1, 3) if x := f.free_count(); x != 6 { @@ -299,7 +308,11 @@ func randomPgids(n int) []pgid { func Test_freelist_init_and_getFreePageIDs(t *testing.T) { f := newFreelist() + if f.IsFreelistTypeArray { + t.Skip() + } exp := []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18} + f.init(exp) if got := f.getFreePageIDs(); !reflect.DeepEqual(exp, got) { @@ -368,6 +381,9 @@ func Test_freelist_mergeWithExist(t *testing.T) { } for _, tt := range tests { f := newFreelist() + if f.IsFreelistTypeArray { + t.Skip() + } f.init(tt.ids) f.mergeWithExistingSpan(tt.pgid) diff --git a/segfreelist.go b/segfreelist.go new file mode 100644 index 000000000..058d69635 --- /dev/null +++ b/segfreelist.go @@ -0,0 +1,325 @@ +package bbolt + +import ( + "fmt" + "sort" + "unsafe" +) + +// initial from pgids using when use seglist version +// pgids must be sorted +func (f *freelist) init(pgids []pgid) { + if len(pgids) == 0 { + return + } + + size := uint64(1) + start := pgids[0] + + if !sort.SliceIsSorted([]pgid(pgids), func(i, j int) bool { return pgids[i] < pgids[j] }) { + panic("pgids not sorted") + } + + f.freemaps = make(map[uint64]pidSet) + f.forwardMap = make(map[pgid]uint64) + f.backwardMap = make(map[pgid]uint64) + + for i := 1; i < len(pgids); i++ { + // continuous page + if pgids[i] == pgids[i-1]+1 { + size++ + } else { + f.addSpan(start, size) + + size = 1 + start = pgids[i] + } + } + + // init the tail + if size != 0 && start != 0 { + f.addSpan(start, size) + } +} + +// free_count returns count of free pages +func (f *freelist) seglistFree_count() int { + // use the forwardmap to get the total count + count := 0 + for _, size := range f.forwardMap { + count += int(size) + } + return count +} + +// getFreePageIDs returns the sorted free page ids +func (f *freelist) getFreePageIDs() []pgid { + if f.IsFreelistTypeArray { + return f.ids + } + m := make([]pgid, 0, f.free_count()) + for start, size := range f.forwardMap { + for i := 0; i < int(size); i++ { + m = append(m, start+pgid(i)) + } + } + sort.Sort(pgids(m)) + + return m +} + +// seglist version +// seglist_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) seglistAllocate(txid txid, n int) pgid { + if n == 0 { + return 0 + } + + // if we have a exact size match just return short path + if bm, ok := f.freemaps[uint64(n)]; ok { + for pid := range bm { + // remove the span + f.delSpan(pid, uint64(n)) + + f.allocs[pid] = txid + + for i := pgid(0); i < pgid(n); i++ { + delete(f.cache, pid+pgid(i)) + } + return pid + } + } + + // lookup the map to find larger span + for size, bm := range f.freemaps { + if size < uint64(n) { + continue + } + + for pid := range bm { + // remove the initial + f.delSpan(pid, uint64(size)) + + f.allocs[pid] = txid + + remain := size - uint64(n) + + // add remain span + f.addSpan(pid+pgid(n), remain) + + for i := pgid(0); i < pgid(n); i++ { + delete(f.cache, pid+pgid(i)) + } + return pid + } + } + + return 0 +} + +// seglist version +// seglistRelease moves all page ids for a transaction id (or older) to the freelist. +func (f *freelist) seglistRelease(txid txid) { + m := make(pgids, 0) + for tid, txp := range f.pending { + if tid <= txid { + // Move transaction's pending pages to the available freelist. + // Don't remove from the cache since the page is still free. + m = append(m, txp.ids...) + delete(f.pending, tid) + } + } + + for _, pgid := range m { + // try to see if we can merge and update + f.mergeWithExistingSpan(pgid) + } +} + +// mergeWithExistingSpan merges pid to the existing free spans, try to merge it backward and forward +func (f *freelist) mergeWithExistingSpan(pid pgid) { + prev := pid - 1 + next := pid + 1 + + preSize, mergeWithPrev := f.backwardMap[prev] + nextSize, mergeWithNext := f.forwardMap[next] + newStart := pid + newSize := uint64(1) + + if mergeWithPrev { + //merge with previous span + start := prev + 1 - pgid(preSize) + f.delSpan(start, preSize) + + newStart -= pgid(preSize) + newSize += preSize + } + + if mergeWithNext { + // merge with next span + f.delSpan(next, nextSize) + newSize += nextSize + } + + f.addSpan(newStart, newSize) +} + +func (f *freelist) addSpan(start pgid, size uint64) { + f.backwardMap[start-1+pgid(size)] = size + f.forwardMap[start] = size + if _, ok := f.freemaps[size]; !ok { + f.freemaps[size] = make(map[pgid]struct{}) + } + + f.freemaps[size][start] = struct{}{} +} + +func (f *freelist) delSpan(start pgid, size uint64) { + delete(f.forwardMap, start) + delete(f.backwardMap, start+pgid(size-1)) + delete(f.freemaps[size], start) + if len(f.freemaps[size]) == 0 { + delete(f.freemaps, size) + } + +} + +// seglistReleaseRange moves pending pages allocated within an extent [begin,end] to the free list. +func (f *freelist) seglistReleaseRange(begin, end txid) { + if begin > end { + return + } + var m pgids + for tid, txp := range f.pending { + if tid < begin || tid > end { + continue + } + // Don't recompute freed pages if ranges haven't updated. + if txp.lastReleaseBegin == begin { + continue + } + for i := 0; i < len(txp.ids); i++ { + if atx := txp.alloctx[i]; atx < begin || atx > end { + continue + } + m = append(m, txp.ids[i]) + txp.ids[i] = txp.ids[len(txp.ids)-1] + txp.ids = txp.ids[:len(txp.ids)-1] + txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1] + txp.alloctx = txp.alloctx[:len(txp.alloctx)-1] + i-- + } + txp.lastReleaseBegin = begin + if len(txp.ids) == 0 { + delete(f.pending, tid) + } + } + + for _, pid := range m { + // try to see if we can merge and update + f.mergeWithExistingSpan(pid) + } +} + +// seglistRollback removes the pages from a given pending tx. +func (f *freelist) seglistRollback(txid txid) { + // Remove page ids from cache. + txp := f.pending[txid] + if txp == nil { + return + } + var m pgids + for i, pgid := range txp.ids { + delete(f.cache, pgid) + tx := txp.alloctx[i] + if tx == 0 { + continue + } + if tx != txid { + // Pending free aborted; restore page back to alloc list. + f.allocs[pgid] = tx + } else { + // Freed page was allocated by this txn; OK to throw away. + m = append(m, pgid) + } + } + // Remove pages from pending list and mark as free if allocated by txid. + delete(f.pending, txid) + + for _, pid := range m { + // try to see if we can merge and update + f.mergeWithExistingSpan(pid) + } +} + +// seglistRead initializes the freelist from a freelist page. +func (f *freelist) seglistRead(p *page) { + if (p.flags & freelistPageFlag) == 0 { + panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.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. + idx, count := 0, int(p.count) + if count == 0xFFFF { + idx = 1 + count = int(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0]) + } + + // Copy the list of page ids from the freelist. + if count != 0 { + ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx : idx+count] + f.init(ids) + } + + // Rebuild the page cache. + f.segListReindex() +} + +// seglistReadIDs initializes the freelist from a given list of ids. +func (f *freelist) seglistReadIDs(ids []pgid) { + f.init(ids) + f.segListReindex() +} + +// reload reads the freelist from a page and filters out pending items. +func (f *freelist) segListReload(p *page) { + f.read(p) + + // Build a cache of only pending pages. + pcache := make(map[pgid]bool) + for _, txp := range f.pending { + for _, pendingID := range txp.ids { + pcache[pendingID] = true + } + } + + // Check each page in the freelist and build a new available freelist + // with any pages not in the pending lists. + var a []pgid + for _, id := range f.getFreePageIDs() { + if !pcache[id] { + a = append(a, id) + } + } + f.init(a) + + // Once the available list is rebuilt then rebuild the free cache so that + // it includes the available and pending free pages. + f.segListReindex() +} + +// segListReindex rebuilds the free cache based on available and pending free lists. +func (f *freelist) segListReindex() { + f.cache = make(map[pgid]bool, f.free_count()) + for start, size := range f.forwardMap { + for i := 0; i < int(size); i++ { + f.cache[pgid(i)+start] = true + } + } + for _, txp := range f.pending { + for _, pendingID := range txp.ids { + f.cache[pendingID] = true + } + } +}