Files
qb/freelist/freelist.go
2026-02-23 11:02:29 +00:00

157 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package freelist
import (
"bytes"
"fmt"
"os"
"slices"
"sync"
"gordenko.dev/dima/qb/bin"
)
const (
pageSize = 1024
crcSize = 4
ptrSize = 4
pointersOnPage = (pageSize - crcSize) / ptrSize
flushTreshold = 1000 //= int(float(pageSize/ptrSize) * 1.3) // кількість вільних сторінок коли вже треба писати на диск
)
type FreeList struct {
mutex sync.Mutex
file *os.File
pages int
//free *roaring.Bitmap
//reserved *roaring.Bitmap
free []uint32
reserved []uint32
}
func New() *FreeList {
return &FreeList{
//free: roaring.New(),
//reserved: roaring.New(),
}
}
func (s *FreeList) Restore(serialized []byte) error {
if (len(serialized) % ptrSize) != 0 {
return fmt.Errorf("wrong size")
}
for i := 0; i < len(serialized); i += ptrSize {
s.free = append(s.free, bin.GetUint32(serialized[i:]))
}
return nil
}
func (s *FreeList) AddPages(pageNumbers []uint32) {
if len(pageNumbers) == 0 {
return
}
s.mutex.Lock()
s.free = append(s.free, pageNumbers...)
if len(s.free) > flushTreshold {
err := s.save()
if err != nil {
// ABORT
}
}
s.mutex.Unlock()
}
func (s *FreeList) save() error {
buf := make([]byte, pageSize)
//w := bytes.NewBuffer(nil)
//s.mutex.Lock()
i := crcSize
for _, pageNo := range s.free[:pointersOnPage] {
bin.PutUint32(buf[i:], pageNo)
i += ptrSize
}
// fix checksum
fileOffset := int64(s.pages-1) * pageSize
n, err := s.file.WriteAt(buf, fileOffset)
if err != nil {
return err
}
if n != pageSize {
return fmt.Errorf("written size %d bytes not equal page size %d", n, pageSize)
}
s.free = s.free[pointersOnPage:]
s.pages++
return nil
}
func (s *FreeList) loadLastPage() error {
var (
buf = make([]byte, pageSize)
offset = int64(s.pages-1) * pageSize
)
n, err := s.file.ReadAt(buf, offset)
if err != nil {
return fmt.Errorf("file.Seek: %s", err)
}
if n != s.pages {
return fmt.Errorf("read size %d bytes not equal page size %d", n, pageSize)
}
// check crc
//var pageNumbers []uint32
for i := crcSize; i < len(buf); i += ptrSize {
s.free = append(s.free, bin.GetUint32(buf[i:]))
}
s.pages--
return nil
}
// ReserveDataPage - аллокатор резервирует страницу, но не удаляет до визова
// DeleteFromFree, ибо транзакция может не завершится, а между віделением страници
// и падением транзакции - будет создан init файл.
func (s *FreeList) ReservePage() (pageNo uint32) {
s.mutex.Lock()
defer s.mutex.Unlock()
if len(s.free) == 0 {
if s.pages == 0 {
return
}
if err := s.loadLastPage(); err != nil {
// ABORT
return
}
}
lastIdx := len(s.free)
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
s.reserved = append(s.reserved, pageNo)
return
}
// Удаляет ранее зарезервированные страницы
func (s *FreeList) DeleteReservedPages(pageNumbers []uint32) {
var cleared []uint32
s.mutex.Lock()
for _, reservedPageNo := range s.reserved {
if slices.Contains(pageNumbers, reservedPageNo) {
// delete
} else {
cleared = append(cleared, reservedPageNo)
}
}
s.reserved = cleared
s.mutex.Unlock()
}
func (s *FreeList) Serialize() ([]byte, error) {
w := bytes.NewBuffer(nil)
s.mutex.Lock()
for _, pageNo := range s.free {
bin.WriteUint32(w, pageNo)
}
for _, pageNo := range s.reserved {
bin.WriteUint32(w, pageNo)
}
s.mutex.Unlock()
return w.Bytes(), nil
}