2026-02-10 14:02:11 +00:00
|
|
|
|
package atree
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"io/fs"
|
|
|
|
|
|
"math"
|
|
|
|
|
|
"os"
|
|
|
|
|
|
|
2026-06-10 06:18:45 +03:00
|
|
|
|
bin "gordenko.dev/dima/bin/little"
|
2026-02-23 11:02:29 +00:00
|
|
|
|
"gordenko.dev/dima/qb"
|
2026-05-21 23:38:52 +03:00
|
|
|
|
"gordenko.dev/dima/qb/util"
|
2026-02-10 14:02:11 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-23 19:37:05 +00:00
|
|
|
|
// fix - додати ID, щоб потім перемістити із тимчасового буфера в pages, або звільнити
|
2026-05-31 20:01:28 +00:00
|
|
|
|
// type AllocatedPage struct {
|
|
|
|
|
|
// PageNo uint32
|
|
|
|
|
|
// Data []byte
|
|
|
|
|
|
// IsReused bool
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
|
|
|
|
|
type readResult struct {
|
|
|
|
|
|
Data []byte
|
|
|
|
|
|
Err error
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// INDEX PAGES
|
|
|
|
|
|
|
2026-02-23 19:37:05 +00:00
|
|
|
|
func (s *Atree) DeletePages(pageNumbers []uint32) {
|
2026-02-10 14:02:11 +00:00
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
for _, pageNo := range pageNumbers {
|
2026-02-23 19:37:05 +00:00
|
|
|
|
delete(s.pages, pageNo)
|
2026-02-10 14:02:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Atree) fetchIndexPage(pageNo uint32) ([]byte, error) {
|
2026-06-10 06:18:45 +03:00
|
|
|
|
// buf, err := s.fetchPage(pageNo)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return nil, err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if buf[pageTypeIdx] != PageTypeIndex {
|
|
|
|
|
|
// return nil, fmt.Errorf("wrong pageType %d instead of %d", buf[pageTypeIdx], PageTypeIndex)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// return buf, nil
|
|
|
|
|
|
return nil, nil
|
2026-02-23 19:37:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Atree) fetchDataPage(pageNo uint32) ([]byte, error) {
|
2026-06-10 06:18:45 +03:00
|
|
|
|
// buf, err := s.fetchPage(pageNo)
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return nil, err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if buf[pageTypeIdx] != PageTypeIndex {
|
|
|
|
|
|
// return nil, fmt.Errorf("wrong pageType %d instead of %d", buf[pageTypeIdx], PageTypeIndex)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// return buf, nil
|
|
|
|
|
|
return nil, nil
|
2026-02-23 19:37:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
func (s *Atree) fetchPage(pageNo uint32) ([]byte, error) {
|
2026-06-10 06:18:45 +03:00
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// p, ok := s.pages[pageNo]
|
|
|
|
|
|
// if ok {
|
|
|
|
|
|
// p.ReferenceCount++
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// return p.Buf, nil
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// resultCh := make(chan readResult, 1)
|
|
|
|
|
|
// s.pageWaits[pageNo] = append(s.pageWaits[pageNo], resultCh)
|
|
|
|
|
|
// if len(s.pageWaits[pageNo]) == 1 {
|
|
|
|
|
|
// s.pagesToRead = append(s.pagesToRead, pageNo)
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// select {
|
|
|
|
|
|
// case s.readSignalCh <- struct{}{}:
|
|
|
|
|
|
// default:
|
|
|
|
|
|
// }
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// result := <-resultCh
|
|
|
|
|
|
// if result.Err == nil {
|
|
|
|
|
|
// result.Err = s.verifyCRC(result.Data, PageSize)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// return result.Data, result.Err
|
|
|
|
|
|
return nil, nil
|
2026-02-10 14:02:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-23 19:37:05 +00:00
|
|
|
|
func (s *Atree) releasePage(pageNo uint32) {
|
2026-02-10 14:02:11 +00:00
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
defer s.mutex.Unlock()
|
|
|
|
|
|
|
2026-02-23 19:37:05 +00:00
|
|
|
|
p, ok := s.pages[pageNo]
|
2026-02-10 14:02:11 +00:00
|
|
|
|
if ok {
|
|
|
|
|
|
if p.ReferenceCount > 0 {
|
|
|
|
|
|
p.ReferenceCount--
|
|
|
|
|
|
return
|
|
|
|
|
|
} else {
|
2026-02-23 11:02:29 +00:00
|
|
|
|
qb.Abort(
|
|
|
|
|
|
qb.ReferenceCountBug,
|
2026-02-23 19:37:05 +00:00
|
|
|
|
fmt.Errorf("call releasePage on page %d with reference count = %d",
|
2026-02-10 14:02:11 +00:00
|
|
|
|
pageNo, p.ReferenceCount),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
func (s *Atree) allocPageNumber() (uint32, bool) {
|
|
|
|
|
|
// fix
|
|
|
|
|
|
return 0, true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 20:01:28 +00:00
|
|
|
|
// func (s *Atree) allocPage() AllocatedPage {
|
|
|
|
|
|
// var (
|
|
|
|
|
|
// allocated = AllocatedPage{
|
|
|
|
|
|
// Data: make([]byte, PageSize),
|
|
|
|
|
|
// }
|
|
|
|
|
|
// )
|
|
|
|
|
|
// //allocated.PageNo = s.freelist.ReservePage()
|
|
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// // if allocated.PageNo > 0 {
|
|
|
|
|
|
// // allocated.IsReused = true
|
|
|
|
|
|
// // } else {
|
|
|
|
|
|
// // if s.allocatedPagesQty == math.MaxUint32 {
|
|
|
|
|
|
// // qb.Abort(qb.MaxAtreeSizeExceeded, errors.New("no space in Atree index"))
|
|
|
|
|
|
// // }
|
|
|
|
|
|
// // s.allocatedPagesQty++
|
|
|
|
|
|
// // allocated.PageNo = s.allocatedPagesQty
|
|
|
|
|
|
// // }
|
|
|
|
|
|
|
|
|
|
|
|
// // s.pages[allocated.PageNo] = &_page{
|
|
|
|
|
|
// // // fix pageType
|
|
|
|
|
|
// // PageNo: allocated.PageNo,
|
|
|
|
|
|
// // Buf: allocated.Data,
|
|
|
|
|
|
// // ReferenceCount: 1,
|
|
|
|
|
|
// // }
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// return allocated
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-02-23 19:37:05 +00:00
|
|
|
|
// // fix - без freelist
|
|
|
|
|
|
// func (s *Atree) allocIndexPage() AllocatedPage {
|
|
|
|
|
|
// var (
|
|
|
|
|
|
// allocated = AllocatedPage{
|
|
|
|
|
|
// Data: make([]byte, PageSize),
|
|
|
|
|
|
// }
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
|
|
// allocated.PageNo = s.freelist.ReservePage()
|
|
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// if allocated.PageNo > 0 {
|
|
|
|
|
|
// allocated.IsReused = true
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// if s.allocatedPagesQty == math.MaxUint32 {
|
|
|
|
|
|
// qb.Abort(qb.MaxAtreeSizeExceeded, errors.New("no space in Atree index"))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// s.allocatedPagesQty++
|
|
|
|
|
|
// allocated.PageNo = s.allocatedPagesQty
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// s.pages[allocated.PageNo] = &_page{
|
|
|
|
|
|
// // fix pageType
|
|
|
|
|
|
// PageNo: allocated.PageNo,
|
|
|
|
|
|
// Buf: allocated.Data,
|
|
|
|
|
|
// ReferenceCount: 1,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// return allocated
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// func (s *Atree) allocDataPage() AllocatedPage {
|
|
|
|
|
|
// var (
|
|
|
|
|
|
// allocated = AllocatedPage{
|
|
|
|
|
|
// Data: make([]byte, PageSize),
|
|
|
|
|
|
// }
|
|
|
|
|
|
// )
|
|
|
|
|
|
|
|
|
|
|
|
// allocated.PageNo = s.freelist.ReservePage()
|
|
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// if allocated.PageNo > 0 {
|
|
|
|
|
|
// allocated.IsReused = true
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// if s.allocatedPagesQty == math.MaxUint32 {
|
|
|
|
|
|
// qb.Abort(qb.MaxAtreeSizeExceeded, errors.New("no space in Atree index"))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// s.allocatedPagesQty++
|
|
|
|
|
|
// allocated.PageNo = s.allocatedPagesQty
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// s.pages[allocated.PageNo] = &_page{
|
|
|
|
|
|
// // fix pageType
|
|
|
|
|
|
// PageNo: allocated.PageNo,
|
|
|
|
|
|
// Buf: allocated.Data,
|
|
|
|
|
|
// ReferenceCount: 1,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// return allocated
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-02-23 19:37:05 +00:00
|
|
|
|
// DATA PAGES
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
|
|
|
|
|
// READ
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Atree) pageReader() {
|
|
|
|
|
|
for {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-s.readSignalCh:
|
|
|
|
|
|
s.readPages()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Atree) readPages() {
|
2026-06-10 06:18:45 +03:00
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// if len(s.pagesToRead) == 0 {
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// return
|
|
|
|
|
|
// }
|
|
|
|
|
|
// pagesToRead := s.pagesToRead
|
|
|
|
|
|
// s.pagesToRead = nil
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
// for _, pageNo := range pagesToRead {
|
|
|
|
|
|
// buf := make([]byte, PageSize)
|
|
|
|
|
|
// off := (pageNo - 1) * PageSize
|
|
|
|
|
|
// n, err := s.file.ReadAt(buf, int64(off))
|
|
|
|
|
|
// if n != PageSize {
|
|
|
|
|
|
// err = fmt.Errorf("read %d instead of %d", n, PageSize)
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// resultChannels := s.pageWaits[pageNo]
|
|
|
|
|
|
// delete(s.pageWaits, pageNo)
|
|
|
|
|
|
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// for _, resultCh := range resultChannels {
|
|
|
|
|
|
// resultCh <- readResult{
|
|
|
|
|
|
// Err: err,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// s.pages[pageNo] = &_page{
|
|
|
|
|
|
// // fix - page type
|
|
|
|
|
|
// PageNo: pageNo,
|
|
|
|
|
|
// Buf: buf,
|
|
|
|
|
|
// ReferenceCount: len(resultChannels),
|
|
|
|
|
|
// }
|
|
|
|
|
|
// s.mutex.Unlock()
|
|
|
|
|
|
// for _, resultCh := range resultChannels {
|
|
|
|
|
|
// resultCh <- readResult{
|
|
|
|
|
|
// Data: buf,
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// WRITE
|
|
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// func (s *Atree) pageWriter() {
|
|
|
|
|
|
// for {
|
|
|
|
|
|
// select {
|
|
|
|
|
|
// case <-s.writeSignalCh:
|
|
|
|
|
|
// err := s.writeTasks()
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// qb.Abort(qb.WriteToAtreeFailed, err)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// type WriteTask struct {
|
|
|
|
|
|
// WaitCh chan struct{}
|
|
|
|
|
|
// Pages []PageToWrite
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// func (s *Atree) appendWriteTaskToQueue(task WriteTask) {
|
|
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// s.writeTasksQueue = append(s.writeTasksQueue, task)
|
|
|
|
|
|
// s.mutex.Unlock()
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// select {
|
|
|
|
|
|
// case s.writeSignalCh <- struct{}{}:
|
|
|
|
|
|
// default:
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// func (s *Atree) writeTasks() error {
|
|
|
|
|
|
// s.mutex.Lock()
|
|
|
|
|
|
// tasks := s.writeTasksQueue
|
|
|
|
|
|
// s.writeTasksQueue = nil
|
|
|
|
|
|
// s.mutex.Unlock()
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// for _, task := range tasks {
|
|
|
|
|
|
// for _, p := range task.Pages {
|
|
|
|
|
|
// if len(p.Data) != PageSize {
|
|
|
|
|
|
// return fmt.Errorf("wrong page %d size: %d",
|
|
|
|
|
|
// p.PageNo, len(p.Data))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// bin.PutUint32(p.Data[crc32Idx:], calcChecksum(p.Data[:crc32Idx]))
|
|
|
|
|
|
|
|
|
|
|
|
// off := (p.PageNo - 1) * PageSize
|
|
|
|
|
|
// n, err := s.file.WriteAt(p.Data, int64(off))
|
|
|
|
|
|
// if err != nil {
|
|
|
|
|
|
// return err
|
|
|
|
|
|
// }
|
|
|
|
|
|
// if n != len(p.Data) {
|
|
|
|
|
|
// return fmt.Errorf("write %d instead of %d", n, len(p.Data))
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// close(task.WaitCh)
|
|
|
|
|
|
// }
|
|
|
|
|
|
// return nil
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
|
|
|
|
|
// IO
|
|
|
|
|
|
|
|
|
|
|
|
func isFileExist(fileName string) (bool, error) {
|
|
|
|
|
|
_, err := os.Stat(fileName)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return true, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func openFile(fileName string, pageSize int) (_ *os.File, _ uint32, err error) {
|
|
|
|
|
|
file, err := os.OpenFile(fileName, os.O_RDWR, filePerm)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
fi, err := file.Stat()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
fileSize := fi.Size()
|
|
|
|
|
|
|
|
|
|
|
|
if (fileSize % int64(pageSize)) > 0 {
|
|
|
|
|
|
err = fmt.Errorf("the file size %d is not a multiple of the page size %d",
|
|
|
|
|
|
fileSize, pageSize)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
allocatedPagesQty := fileSize / int64(pageSize)
|
|
|
|
|
|
if allocatedPagesQty > math.MaxUint32 {
|
|
|
|
|
|
err = fmt.Errorf("allocated pages %d is > max pages %d",
|
|
|
|
|
|
allocatedPagesQty, math.MaxUint32)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return file, uint32(allocatedPagesQty), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-10 00:59:47 +00:00
|
|
|
|
// func (s *Atree) ApplyREDO(task WriteTask) {
|
|
|
|
|
|
// s.appendWriteTaskToQueue(task)
|
|
|
|
|
|
// }
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
|
|
|
|
|
func (s *Atree) verifyCRC(data []byte, pageSize int) error {
|
|
|
|
|
|
var (
|
|
|
|
|
|
pos = pageSize - 4
|
2026-05-21 23:38:52 +03:00
|
|
|
|
calculatedCRC = util.CalcChecksum(data[:pos])
|
2026-06-10 06:18:45 +03:00
|
|
|
|
storedCRC, _ = bin.GetUint32(data[pos:])
|
2026-02-10 14:02:11 +00:00
|
|
|
|
)
|
|
|
|
|
|
if calculatedCRC != storedCRC {
|
|
|
|
|
|
return fmt.Errorf("calculatedCRC %d not equal storedCRC %d",
|
|
|
|
|
|
calculatedCRC, storedCRC)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|