157 lines
3.5 KiB
Go
157 lines
3.5 KiB
Go
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
|
||
}
|