 9bdcbe0447
			
		
	
	9bdcbe0447
	
	
	
		
			
			Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			311 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package freelist
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 	"sort"
 | |
| 	"unsafe"
 | |
| 
 | |
| 	"go.etcd.io/bbolt/internal/common"
 | |
| )
 | |
| 
 | |
| type txPending struct {
 | |
| 	ids              []common.Pgid
 | |
| 	alloctx          []common.Txid // txids allocating the ids
 | |
| 	lastReleaseBegin common.Txid   // beginning txid of last matching releaseRange
 | |
| }
 | |
| 
 | |
| type shared struct {
 | |
| 	Interface
 | |
| 
 | |
| 	readonlyTXIDs []common.Txid               // all readonly transaction IDs.
 | |
| 	allocs        map[common.Pgid]common.Txid // mapping of Txid that allocated a pgid.
 | |
| 	cache         map[common.Pgid]struct{}    // fast lookup of all free and pending page ids.
 | |
| 	pending       map[common.Txid]*txPending  // mapping of soon-to-be free page ids by tx.
 | |
| }
 | |
| 
 | |
| func newShared() *shared {
 | |
| 	return &shared{
 | |
| 		pending: make(map[common.Txid]*txPending),
 | |
| 		allocs:  make(map[common.Pgid]common.Txid),
 | |
| 		cache:   make(map[common.Pgid]struct{}),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *shared) pendingPageIds() map[common.Txid]*txPending {
 | |
| 	return t.pending
 | |
| }
 | |
| 
 | |
| func (t *shared) PendingCount() int {
 | |
| 	var count int
 | |
| 	for _, txp := range t.pending {
 | |
| 		count += len(txp.ids)
 | |
| 	}
 | |
| 	return count
 | |
| }
 | |
| 
 | |
| func (t *shared) Count() int {
 | |
| 	return t.FreeCount() + t.PendingCount()
 | |
| }
 | |
| 
 | |
| func (t *shared) Freed(pgId common.Pgid) bool {
 | |
| 	_, ok := t.cache[pgId]
 | |
| 	return ok
 | |
| }
 | |
| 
 | |
| func (t *shared) Free(txid common.Txid, p *common.Page) {
 | |
| 	if p.Id() <= 1 {
 | |
| 		panic(fmt.Sprintf("cannot free page 0 or 1: %d", p.Id()))
 | |
| 	}
 | |
| 
 | |
| 	// Free page and all its overflow pages.
 | |
| 	txp := t.pending[txid]
 | |
| 	if txp == nil {
 | |
| 		txp = &txPending{}
 | |
| 		t.pending[txid] = txp
 | |
| 	}
 | |
| 	allocTxid, ok := t.allocs[p.Id()]
 | |
| 	common.Verify(func() {
 | |
| 		if allocTxid == txid {
 | |
| 			panic(fmt.Sprintf("free: freed page (%d) was allocated by the same transaction (%d)", p.Id(), txid))
 | |
| 		}
 | |
| 	})
 | |
| 	if ok {
 | |
| 		delete(t.allocs, p.Id())
 | |
| 	}
 | |
| 
 | |
| 	for id := p.Id(); id <= p.Id()+common.Pgid(p.Overflow()); id++ {
 | |
| 		// Verify that page is not already free.
 | |
| 		if _, ok := t.cache[id]; ok {
 | |
| 			panic(fmt.Sprintf("page %d already freed", id))
 | |
| 		}
 | |
| 		// Add to the freelist and cache.
 | |
| 		txp.ids = append(txp.ids, id)
 | |
| 		txp.alloctx = append(txp.alloctx, allocTxid)
 | |
| 		t.cache[id] = struct{}{}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *shared) Rollback(txid common.Txid) {
 | |
| 	// Remove page ids from cache.
 | |
| 	txp := t.pending[txid]
 | |
| 	if txp == nil {
 | |
| 		return
 | |
| 	}
 | |
| 	for i, pgid := range txp.ids {
 | |
| 		delete(t.cache, pgid)
 | |
| 		tx := txp.alloctx[i]
 | |
| 		if tx == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if tx != txid {
 | |
| 			// Pending free aborted; restore page back to alloc list.
 | |
| 			t.allocs[pgid] = tx
 | |
| 		} else {
 | |
| 			// A writing TXN should never free a page which was allocated by itself.
 | |
| 			panic(fmt.Sprintf("rollback: freed page (%d) was allocated by the same transaction (%d)", pgid, txid))
 | |
| 		}
 | |
| 	}
 | |
| 	// Remove pages from pending list and mark as free if allocated by txid.
 | |
| 	delete(t.pending, txid)
 | |
| 
 | |
| 	// Remove pgids which are allocated by this txid
 | |
| 	for pgid, tid := range t.allocs {
 | |
| 		if tid == txid {
 | |
| 			delete(t.allocs, pgid)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *shared) AddReadonlyTXID(tid common.Txid) {
 | |
| 	t.readonlyTXIDs = append(t.readonlyTXIDs, tid)
 | |
| }
 | |
| 
 | |
| func (t *shared) RemoveReadonlyTXID(tid common.Txid) {
 | |
| 	for i := range t.readonlyTXIDs {
 | |
| 		if t.readonlyTXIDs[i] == tid {
 | |
| 			last := len(t.readonlyTXIDs) - 1
 | |
| 			t.readonlyTXIDs[i] = t.readonlyTXIDs[last]
 | |
| 			t.readonlyTXIDs = t.readonlyTXIDs[:last]
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type txIDx []common.Txid
 | |
| 
 | |
| func (t txIDx) Len() int           { return len(t) }
 | |
| func (t txIDx) Swap(i, j int)      { t[i], t[j] = t[j], t[i] }
 | |
| func (t txIDx) Less(i, j int) bool { return t[i] < t[j] }
 | |
| 
 | |
| func (t *shared) ReleasePendingPages() {
 | |
| 	// Free all pending pages prior to the earliest open transaction.
 | |
| 	sort.Sort(txIDx(t.readonlyTXIDs))
 | |
| 	minid := common.Txid(math.MaxUint64)
 | |
| 	if len(t.readonlyTXIDs) > 0 {
 | |
| 		minid = t.readonlyTXIDs[0]
 | |
| 	}
 | |
| 	if minid > 0 {
 | |
| 		t.release(minid - 1)
 | |
| 	}
 | |
| 	// Release unused txid extents.
 | |
| 	for _, tid := range t.readonlyTXIDs {
 | |
| 		t.releaseRange(minid, tid-1)
 | |
| 		minid = tid + 1
 | |
| 	}
 | |
| 	t.releaseRange(minid, common.Txid(math.MaxUint64))
 | |
| 	// Any page both allocated and freed in an extent is safe to release.
 | |
| }
 | |
| 
 | |
| func (t *shared) release(txid common.Txid) {
 | |
| 	m := make(common.Pgids, 0)
 | |
| 	for tid, txp := range t.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(t.pending, tid)
 | |
| 		}
 | |
| 	}
 | |
| 	t.mergeSpans(m)
 | |
| }
 | |
| 
 | |
| func (t *shared) releaseRange(begin, end common.Txid) {
 | |
| 	if begin > end {
 | |
| 		return
 | |
| 	}
 | |
| 	m := common.Pgids{}
 | |
| 	for tid, txp := range t.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(t.pending, tid)
 | |
| 		}
 | |
| 	}
 | |
| 	t.mergeSpans(m)
 | |
| }
 | |
| 
 | |
| // 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 (t *shared) Copyall(dst []common.Pgid) {
 | |
| 	m := make(common.Pgids, 0, t.PendingCount())
 | |
| 	for _, txp := range t.pendingPageIds() {
 | |
| 		m = append(m, txp.ids...)
 | |
| 	}
 | |
| 	sort.Sort(m)
 | |
| 	common.Mergepgids(dst, t.freePageIds(), m)
 | |
| }
 | |
| 
 | |
| func (t *shared) Reload(p *common.Page) {
 | |
| 	t.Read(p)
 | |
| 	t.NoSyncReload(t.freePageIds())
 | |
| }
 | |
| 
 | |
| func (t *shared) NoSyncReload(pgIds common.Pgids) {
 | |
| 	// Build a cache of only pending pages.
 | |
| 	pcache := make(map[common.Pgid]bool)
 | |
| 	for _, txp := range t.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.
 | |
| 	a := []common.Pgid{}
 | |
| 	for _, id := range pgIds {
 | |
| 		if !pcache[id] {
 | |
| 			a = append(a, id)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	t.Init(a)
 | |
| }
 | |
| 
 | |
| // reindex rebuilds the free cache based on available and pending free lists.
 | |
| func (t *shared) reindex() {
 | |
| 	free := t.freePageIds()
 | |
| 	pending := t.pendingPageIds()
 | |
| 	t.cache = make(map[common.Pgid]struct{}, len(free))
 | |
| 	for _, id := range free {
 | |
| 		t.cache[id] = struct{}{}
 | |
| 	}
 | |
| 	for _, txp := range pending {
 | |
| 		for _, pendingID := range txp.ids {
 | |
| 			t.cache[pendingID] = struct{}{}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *shared) Read(p *common.Page) {
 | |
| 	if !p.IsFreelistPage() {
 | |
| 		panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", p.Id(), p.Typ()))
 | |
| 	}
 | |
| 
 | |
| 	ids := p.FreelistPageIds()
 | |
| 
 | |
| 	// Copy the list of page ids from the freelist.
 | |
| 	if len(ids) == 0 {
 | |
| 		t.Init([]common.Pgid{})
 | |
| 	} else {
 | |
| 		// copy the ids, so we don't modify on the freelist page directly
 | |
| 		idsCopy := make([]common.Pgid, len(ids))
 | |
| 		copy(idsCopy, ids)
 | |
| 		// Make sure they're sorted.
 | |
| 		sort.Sort(common.Pgids(idsCopy))
 | |
| 
 | |
| 		t.Init(idsCopy)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (t *shared) EstimatedWritePageSize() int {
 | |
| 	n := t.Count()
 | |
| 	if n >= 0xFFFF {
 | |
| 		// The first element will be used to store the count. See freelist.write.
 | |
| 		n++
 | |
| 	}
 | |
| 	return int(common.PageHeaderSize) + (int(unsafe.Sizeof(common.Pgid(0))) * n)
 | |
| }
 | |
| 
 | |
| func (t *shared) Write(p *common.Page) {
 | |
| 	// Combine the old free pgids and pgids waiting on an open transaction.
 | |
| 
 | |
| 	// Update the header flag.
 | |
| 	p.SetFlags(common.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.
 | |
| 	l := t.Count()
 | |
| 	if l == 0 {
 | |
| 		p.SetCount(uint16(l))
 | |
| 	} else if l < 0xFFFF {
 | |
| 		p.SetCount(uint16(l))
 | |
| 		data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
 | |
| 		ids := unsafe.Slice((*common.Pgid)(data), l)
 | |
| 		t.Copyall(ids)
 | |
| 	} else {
 | |
| 		p.SetCount(0xFFFF)
 | |
| 		data := common.UnsafeAdd(unsafe.Pointer(p), unsafe.Sizeof(*p))
 | |
| 		ids := unsafe.Slice((*common.Pgid)(data), l+1)
 | |
| 		ids[0] = common.Pgid(l)
 | |
| 		t.Copyall(ids[1:])
 | |
| 	}
 | |
| }
 |