wp
This commit is contained in:
@@ -4,37 +4,139 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"gordenko.dev/dima/qb/bin"
|
||||
"gordenko.dev/dima/qb/util"
|
||||
)
|
||||
|
||||
const (
|
||||
pageSize = 1024
|
||||
crcSize = 4
|
||||
ptrSize = 4
|
||||
pointersOnPage = (pageSize - crcSize) / ptrSize
|
||||
flushTreshold = 1000 //= int(float(pageSize/ptrSize) * 1.3) // кількість вільних сторінок коли вже треба писати на диск
|
||||
crcSize = 4
|
||||
ptrSize = 4
|
||||
)
|
||||
|
||||
type FreeList struct {
|
||||
mutex sync.Mutex
|
||||
file *os.File
|
||||
pages int
|
||||
//free *roaring.Bitmap
|
||||
//reserved *roaring.Bitmap
|
||||
free []uint32
|
||||
reserved []uint32
|
||||
file *os.File
|
||||
pageSize int
|
||||
pointersOnPage int
|
||||
flushTreshold int // кількість вільних сторінок коли вже треба писати на диск
|
||||
pages int
|
||||
free []uint32
|
||||
}
|
||||
|
||||
func New() *FreeList {
|
||||
return &FreeList{
|
||||
//free: roaring.New(),
|
||||
//reserved: roaring.New(),
|
||||
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")
|
||||
@@ -45,112 +147,11 @@ func (s *FreeList) Restore(serialized []byte) error {
|
||||
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()
|
||||
}
|
||||
|
||||
// для запису в snapshot
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user