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 }