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 }