Files
qb/freelist/freelist.go
2026-05-21 23:38:52 +03:00

158 lines
3.4 KiB
Go

package freelist
import (
"bytes"
"fmt"
"os"
"gordenko.dev/dima/qb/bin"
"gordenko.dev/dima/qb/util"
)
const (
crcSize = 4
ptrSize = 4
)
type FreeList struct {
file *os.File
pageSize int
pointersOnPage int
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
pages int
free []uint32
}
type Options struct {
PageSize int
File *os.File
}
func New(opt Options) (*FreeList, error) {
s := &FreeList{
file: opt.File,
pageSize: opt.PageSize,
pointersOnPage: (opt.PageSize - crcSize) / ptrSize,
}
s.flushTreshold = int(float64(s.pointersOnPage) * 1.3)
info, err := s.file.Stat()
if err != nil {
return nil, err
}
fileSize := info.Size()
if fileSize > 0 {
if (fileSize % int64(s.pageSize)) > 0 {
return nil, fmt.Errorf("file size %d is not a multiple of page size %d", fileSize, s.pageSize)
}
s.pages = int(fileSize) / s.pageSize
}
return s, nil
}
func (s *FreeList) AddPageNumbers(pageNumbers []uint32) (err error) {
if len(pageNumbers) == 0 {
return
}
s.free = append(s.free, pageNumbers...)
for len(s.free) > s.flushTreshold {
err = s.save()
if err != nil {
return
}
// викидаю номери сторінок, які записав на диск
copy(s.free, s.free[s.pointersOnPage:])
s.free = s.free[:len(s.free)-s.pointersOnPage]
}
return
}
func (s *FreeList) save() error {
buf := make([]byte, s.pageSize)
i := crcSize
for _, pageNo := range s.free[:s.pointersOnPage] {
bin.PutUint32(buf[i:], pageNo)
i += ptrSize
}
bin.PutUint32(buf, util.CalcChecksum(buf[crcSize:]))
//
off := int64(s.pages * s.pageSize)
n, err := s.file.WriteAt(buf, off)
if err != nil {
return err
}
if n != s.pageSize {
return fmt.Errorf("written size %d bytes not equal page size %d", n, s.pageSize)
}
err = s.file.Sync()
if err != nil {
return err
}
s.pages++
return nil
}
func (s *FreeList) loadLastPage() error {
var (
buf = make([]byte, s.pageSize)
off = int64((s.pages - 1) * s.pageSize)
)
n, err := s.file.ReadAt(buf, off)
if err != nil {
return fmt.Errorf("file.Seek: %s", err)
}
if n != s.pageSize {
return fmt.Errorf("read size %d bytes not equal page size %d", n, s.pageSize)
}
// check crc
checksum := bin.GetUint32(buf[0:])
if util.CalcChecksum(buf[crcSize:]) != checksum {
return fmt.Errorf("page %d is corrupted", s.pages)
}
for i := crcSize; i < len(buf); i += ptrSize {
s.free = append(s.free, bin.GetUint32(buf[i:]))
}
s.pages--
err = s.file.Truncate(int64(s.pages * s.pageSize))
if err != nil {
return err
}
return nil
}
func (s *FreeList) GetPageNumber() (pageNo uint32, err error) {
if len(s.free) == 0 {
if s.pages == 0 {
return
}
err = s.loadLastPage()
if err != nil {
return
}
}
lastIdx := len(s.free) - 1
pageNo = s.free[lastIdx]
s.free = s.free[:lastIdx]
return
}
// для відновлення недозаповненнох сторінки із снапшота
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
}
// для запису в snapshot
func (s *FreeList) Serialize() ([]byte, error) {
w := bytes.NewBuffer(nil)
for _, pageNo := range s.free {
bin.WriteUint32(w, pageNo)
}
return w.Bytes(), nil
}