github.com/bhojpur/cache@v0.0.4/pkg/memory/freelist.go (about)

     1  package memory
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import (
    24  	"fmt"
    25  	"sort"
    26  	"unsafe"
    27  )
    28  
    29  // txPending holds a list of pgids and corresponding allocation txns
    30  // that are pending to be freed.
    31  type txPending struct {
    32  	ids              []pgid
    33  	alloctx          []txid // txids allocating the ids
    34  	lastReleaseBegin txid   // beginning txid of last matching releaseRange
    35  }
    36  
    37  // pidSet holds the set of starting pgids which have the same span size
    38  type pidSet map[pgid]struct{}
    39  
    40  // freelist represents a list of all pages that are available for allocation.
    41  // It also tracks pages that have been freed but are still in use by open transactions.
    42  type freelist struct {
    43  	freelistType   FreelistType                // freelist type
    44  	ids            []pgid                      // all free and available free page ids.
    45  	allocs         map[pgid]txid               // mapping of txid that allocated a pgid.
    46  	pending        map[txid]*txPending         // mapping of soon-to-be free page ids by tx.
    47  	cache          map[pgid]bool               // fast lookup of all free and pending page ids.
    48  	freemaps       map[uint64]pidSet           // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
    49  	forwardMap     map[pgid]uint64             // key is start pgid, value is its span size
    50  	backwardMap    map[pgid]uint64             // key is end pgid, value is its span size
    51  	allocate       func(txid txid, n int) pgid // the freelist allocate func
    52  	free_count     func() int                  // the function which gives you free page number
    53  	mergeSpans     func(ids pgids)             // the mergeSpan func
    54  	getFreePageIDs func() []pgid               // get free pgids func
    55  	readIDs        func(pgids []pgid)          // readIDs func reads list of pages and init the freelist
    56  }
    57  
    58  // newFreelist returns an empty, initialized freelist.
    59  func newFreelist(freelistType FreelistType) *freelist {
    60  	f := &freelist{
    61  		freelistType: freelistType,
    62  		allocs:       make(map[pgid]txid),
    63  		pending:      make(map[txid]*txPending),
    64  		cache:        make(map[pgid]bool),
    65  		freemaps:     make(map[uint64]pidSet),
    66  		forwardMap:   make(map[pgid]uint64),
    67  		backwardMap:  make(map[pgid]uint64),
    68  	}
    69  
    70  	if freelistType == FreelistMapType {
    71  		f.allocate = f.hashmapAllocate
    72  		f.free_count = f.hashmapFreeCount
    73  		f.mergeSpans = f.hashmapMergeSpans
    74  		f.getFreePageIDs = f.hashmapGetFreePageIDs
    75  		f.readIDs = f.hashmapReadIDs
    76  	} else {
    77  		f.allocate = f.arrayAllocate
    78  		f.free_count = f.arrayFreeCount
    79  		f.mergeSpans = f.arrayMergeSpans
    80  		f.getFreePageIDs = f.arrayGetFreePageIDs
    81  		f.readIDs = f.arrayReadIDs
    82  	}
    83  
    84  	return f
    85  }
    86  
    87  // size returns the size of the page after serialization.
    88  func (f *freelist) size() int {
    89  	n := f.count()
    90  	if n >= 0xFFFF {
    91  		// The first element will be used to store the count. See freelist.write.
    92  		n++
    93  	}
    94  	return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * n)
    95  }
    96  
    97  // count returns count of pages on the freelist
    98  func (f *freelist) count() int {
    99  	return f.free_count() + f.pending_count()
   100  }
   101  
   102  // arrayFreeCount returns count of free pages(array version)
   103  func (f *freelist) arrayFreeCount() int {
   104  	return len(f.ids)
   105  }
   106  
   107  // pending_count returns count of pending pages
   108  func (f *freelist) pending_count() int {
   109  	var count int
   110  	for _, txp := range f.pending {
   111  		count += len(txp.ids)
   112  	}
   113  	return count
   114  }
   115  
   116  // copyall copies a list of all free ids and all pending ids in one sorted list.
   117  // f.count returns the minimum length required for dst.
   118  func (f *freelist) copyall(dst []pgid) {
   119  	m := make(pgids, 0, f.pending_count())
   120  	for _, txp := range f.pending {
   121  		m = append(m, txp.ids...)
   122  	}
   123  	sort.Sort(m)
   124  	mergepgids(dst, f.getFreePageIDs(), m)
   125  }
   126  
   127  // arrayAllocate returns the starting page id of a contiguous list of pages of a given size.
   128  // If a contiguous block cannot be found then 0 is returned.
   129  func (f *freelist) arrayAllocate(txid txid, n int) pgid {
   130  	if len(f.ids) == 0 {
   131  		return 0
   132  	}
   133  
   134  	var initial, previd pgid
   135  	for i, id := range f.ids {
   136  		if id <= 1 {
   137  			panic(fmt.Sprintf("invalid page allocation: %d", id))
   138  		}
   139  
   140  		// Reset initial page if this is not contiguous.
   141  		if previd == 0 || id-previd != 1 {
   142  			initial = id
   143  		}
   144  
   145  		// If we found a contiguous block then remove it and return it.
   146  		if (id-initial)+1 == pgid(n) {
   147  			// If we're allocating off the beginning then take the fast path
   148  			// and just adjust the existing slice. This will use extra memory
   149  			// temporarily but the append() in free() will realloc the slice
   150  			// as is necessary.
   151  			if (i + 1) == n {
   152  				f.ids = f.ids[i+1:]
   153  			} else {
   154  				copy(f.ids[i-n+1:], f.ids[i+1:])
   155  				f.ids = f.ids[:len(f.ids)-n]
   156  			}
   157  
   158  			// Remove from the free cache.
   159  			for i := pgid(0); i < pgid(n); i++ {
   160  				delete(f.cache, initial+i)
   161  			}
   162  			f.allocs[initial] = txid
   163  			return initial
   164  		}
   165  
   166  		previd = id
   167  	}
   168  	return 0
   169  }
   170  
   171  // free releases a page and its overflow for a given transaction id.
   172  // If the page is already free then a panic will occur.
   173  func (f *freelist) free(txid txid, p *page) {
   174  	if p.id <= 1 {
   175  		panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.id))
   176  	}
   177  
   178  	// Free page and all its overflow pages.
   179  	txp := f.pending[txid]
   180  	if txp == nil {
   181  		txp = &txPending{}
   182  		f.pending[txid] = txp
   183  	}
   184  	allocTxid, ok := f.allocs[p.id]
   185  	if ok {
   186  		delete(f.allocs, p.id)
   187  	} else if (p.flags & freelistPageFlag) != 0 {
   188  		// Freelist is always allocated by prior tx.
   189  		allocTxid = txid - 1
   190  	}
   191  
   192  	for id := p.id; id <= p.id+pgid(p.overflow); id++ {
   193  		// Verify that page is not already free.
   194  		if f.cache[id] {
   195  			panic(fmt.Sprintf("page %d already freed", id))
   196  		}
   197  		// Add to the freelist and cache.
   198  		txp.ids = append(txp.ids, id)
   199  		txp.alloctx = append(txp.alloctx, allocTxid)
   200  		f.cache[id] = true
   201  	}
   202  }
   203  
   204  // release moves all page ids for a transaction id (or older) to the freelist.
   205  func (f *freelist) release(txid txid) {
   206  	m := make(pgids, 0)
   207  	for tid, txp := range f.pending {
   208  		if tid <= txid {
   209  			// Move transaction's pending pages to the available freelist.
   210  			// Don't remove from the cache since the page is still free.
   211  			m = append(m, txp.ids...)
   212  			delete(f.pending, tid)
   213  		}
   214  	}
   215  	f.mergeSpans(m)
   216  }
   217  
   218  // releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
   219  func (f *freelist) releaseRange(begin, end txid) {
   220  	if begin > end {
   221  		return
   222  	}
   223  	var m pgids
   224  	for tid, txp := range f.pending {
   225  		if tid < begin || tid > end {
   226  			continue
   227  		}
   228  		// Don't recompute freed pages if ranges haven't updated.
   229  		if txp.lastReleaseBegin == begin {
   230  			continue
   231  		}
   232  		for i := 0; i < len(txp.ids); i++ {
   233  			if atx := txp.alloctx[i]; atx < begin || atx > end {
   234  				continue
   235  			}
   236  			m = append(m, txp.ids[i])
   237  			txp.ids[i] = txp.ids[len(txp.ids)-1]
   238  			txp.ids = txp.ids[:len(txp.ids)-1]
   239  			txp.alloctx[i] = txp.alloctx[len(txp.alloctx)-1]
   240  			txp.alloctx = txp.alloctx[:len(txp.alloctx)-1]
   241  			i--
   242  		}
   243  		txp.lastReleaseBegin = begin
   244  		if len(txp.ids) == 0 {
   245  			delete(f.pending, tid)
   246  		}
   247  	}
   248  	f.mergeSpans(m)
   249  }
   250  
   251  // rollback removes the pages from a given pending tx.
   252  func (f *freelist) rollback(txid txid) {
   253  	// Remove page ids from cache.
   254  	txp := f.pending[txid]
   255  	if txp == nil {
   256  		return
   257  	}
   258  	var m pgids
   259  	for i, pgid := range txp.ids {
   260  		delete(f.cache, pgid)
   261  		tx := txp.alloctx[i]
   262  		if tx == 0 {
   263  			continue
   264  		}
   265  		if tx != txid {
   266  			// Pending free aborted; restore page back to alloc list.
   267  			f.allocs[pgid] = tx
   268  		} else {
   269  			// Freed page was allocated by this txn; OK to throw away.
   270  			m = append(m, pgid)
   271  		}
   272  	}
   273  	// Remove pages from pending list and mark as free if allocated by txid.
   274  	delete(f.pending, txid)
   275  	f.mergeSpans(m)
   276  }
   277  
   278  // freed returns whether a given page is in the free list.
   279  func (f *freelist) freed(pgid pgid) bool {
   280  	return f.cache[pgid]
   281  }
   282  
   283  // read initializes the freelist from a freelist page.
   284  func (f *freelist) read(p *page) {
   285  	if (p.flags & freelistPageFlag) == 0 {
   286  		panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.id, p.typ()))
   287  	}
   288  	// If the page.count is at the max uint16 value (64k) then it's considered
   289  	// an overflow and the size of the freelist is stored as the first element.
   290  	var idx, count = 0, int(p.count)
   291  	if count == 0xFFFF {
   292  		idx = 1
   293  		c := *(*pgid)(unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p)))
   294  		count = int(c)
   295  		if count < 0 {
   296  			panic(fmt.Sprintf("leading element count %d overflows int", c))
   297  		}
   298  	}
   299  
   300  	// Copy the list of page ids from the freelist.
   301  	if count == 0 {
   302  		f.ids = nil
   303  	} else {
   304  		var ids []pgid
   305  		data := unsafeIndex(unsafe.Pointer(p), unsafe.Sizeof(*p), unsafe.Sizeof(ids[0]), idx)
   306  		unsafeSlice(unsafe.Pointer(&ids), data, count)
   307  
   308  		// copy the ids, so we don't modify on the freelist page directly
   309  		idsCopy := make([]pgid, count)
   310  		copy(idsCopy, ids)
   311  		// Make sure they're sorted.
   312  		sort.Sort(pgids(idsCopy))
   313  
   314  		f.readIDs(idsCopy)
   315  	}
   316  }
   317  
   318  // arrayReadIDs initializes the freelist from a given list of ids.
   319  func (f *freelist) arrayReadIDs(ids []pgid) {
   320  	f.ids = ids
   321  	f.reindex()
   322  }
   323  
   324  func (f *freelist) arrayGetFreePageIDs() []pgid {
   325  	return f.ids
   326  }
   327  
   328  // write writes the page ids onto a freelist page. All free and pending ids are
   329  // saved to disk since in the event of a program crash, all pending ids will
   330  // become free.
   331  func (f *freelist) write(p *page) error {
   332  	// Combine the old free pgids and pgids waiting on an open transaction.
   333  
   334  	// Update the header flag.
   335  	p.flags |= freelistPageFlag
   336  
   337  	// The page.count can only hold up to 64k elements so if we overflow that
   338  	// number then we handle it by putting the size in the first element.
   339  	l := f.count()
   340  	if l == 0 {
   341  		p.count = uint16(l)
   342  	} else if l < 0xFFFF {
   343  		p.count = uint16(l)
   344  		var ids []pgid
   345  		data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
   346  		unsafeSlice(unsafe.Pointer(&ids), data, l)
   347  		f.copyall(ids)
   348  	} else {
   349  		p.count = 0xFFFF
   350  		var ids []pgid
   351  		data := unsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
   352  		unsafeSlice(unsafe.Pointer(&ids), data, l+1)
   353  		ids[0] = pgid(l)
   354  		f.copyall(ids[1:])
   355  	}
   356  
   357  	return nil
   358  }
   359  
   360  // reload reads the freelist from a page and filters out pending items.
   361  func (f *freelist) reload(p *page) {
   362  	f.read(p)
   363  
   364  	// Build a cache of only pending pages.
   365  	pcache := make(map[pgid]bool)
   366  	for _, txp := range f.pending {
   367  		for _, pendingID := range txp.ids {
   368  			pcache[pendingID] = true
   369  		}
   370  	}
   371  
   372  	// Check each page in the freelist and build a new available freelist
   373  	// with any pages not in the pending lists.
   374  	var a []pgid
   375  	for _, id := range f.getFreePageIDs() {
   376  		if !pcache[id] {
   377  			a = append(a, id)
   378  		}
   379  	}
   380  
   381  	f.readIDs(a)
   382  }
   383  
   384  // noSyncReload reads the freelist from pgids and filters out pending items.
   385  func (f *freelist) noSyncReload(pgids []pgid) {
   386  	// Build a cache of only pending pages.
   387  	pcache := make(map[pgid]bool)
   388  	for _, txp := range f.pending {
   389  		for _, pendingID := range txp.ids {
   390  			pcache[pendingID] = true
   391  		}
   392  	}
   393  
   394  	// Check each page in the freelist and build a new available freelist
   395  	// with any pages not in the pending lists.
   396  	var a []pgid
   397  	for _, id := range pgids {
   398  		if !pcache[id] {
   399  			a = append(a, id)
   400  		}
   401  	}
   402  
   403  	f.readIDs(a)
   404  }
   405  
   406  // reindex rebuilds the free cache based on available and pending free lists.
   407  func (f *freelist) reindex() {
   408  	ids := f.getFreePageIDs()
   409  	f.cache = make(map[pgid]bool, len(ids))
   410  	for _, id := range ids {
   411  		f.cache[id] = true
   412  	}
   413  	for _, txp := range f.pending {
   414  		for _, pendingID := range txp.ids {
   415  			f.cache[pendingID] = true
   416  		}
   417  	}
   418  }
   419  
   420  // arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using array
   421  func (f *freelist) arrayMergeSpans(ids pgids) {
   422  	sort.Sort(ids)
   423  	f.ids = pgids(f.ids).merge(ids)
   424  }