package freelist import ( "fmt" "io" "os" bin "gordenko.dev/dima/bin/little" "gordenko.dev/dima/qb/util" ) /* Ідея - я фіксую кількість сторінок записаних в снапшоті. Додаю і видаляю сторінки із основного файла, поки їх кількість > кількості зафіксованих. Якщо pageNumbers видалено і кількість сторінок треба зменшити - нові сторінки пишу в delta файл. При створенні снапшота - сторінки із delta файла записую в основний файл і фіксую в снапшоті їх кількість. PageNumbers із буфера пишу в снапшот. */ const ( crcSize = 4 ptrSize = 4 ) type FreeList struct { baseFile *os.File deltaFile *os.File pageSize int pointersOnPage int flushTreshold int // кількість вільних сторінок коли вже треба писати на диск frozenPageCount int basePagesCount int deltaPagesCount int free []uint32 } type Options struct { PageSize int BaseFilePath string DeltaFilePath string FrozenPageCount int } func New(opt Options) (*FreeList, error) { s := &FreeList{ pageSize: opt.PageSize, pointersOnPage: (opt.PageSize - crcSize) / ptrSize, frozenPageCount: opt.FrozenPageCount, basePagesCount: opt.FrozenPageCount, } s.flushTreshold = int(float64(s.pointersOnPage) * 1.3) var err error s.baseFile, err = os.OpenFile(opt.BaseFilePath, os.O_RDWR|os.O_CREATE, 0666) if err != nil { return nil, err } s.deltaFile, err = os.OpenFile(opt.DeltaFilePath, os.O_RDWR|os.O_CREATE, 0666) if err != nil { return nil, err } // info, err := s.baseFile.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.basePagesCount = int(fileSize) / s.pageSize // } return s, nil } func (s *FreeList) Pages() int { return s.basePagesCount + s.deltaPagesCount } 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] fmt.Println("free:", s.free) } return } func (s *FreeList) save() error { fmt.Println("save:", s.free[:s.pointersOnPage]) 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:])) // var ( file *os.File off int64 ) if s.deltaPagesCount > 0 || s.basePagesCount < s.frozenPageCount { file = s.deltaFile off = int64(s.deltaPagesCount * s.pageSize) s.deltaPagesCount++ } else { file = s.baseFile off = int64(s.basePagesCount * s.pageSize) s.basePagesCount++ } fmt.Println("write at:", off) n, err := 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 = file.Sync() if err != nil { return err } return nil } func (s *FreeList) loadLastPage() error { var ( file *os.File off int64 buf = make([]byte, s.pageSize) ) if s.deltaPagesCount > 0 { file = s.deltaFile off = int64((s.deltaPagesCount - 1) * s.pageSize) } else { file = s.baseFile off = int64((s.basePagesCount - 1) * s.pageSize) } n, err := 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.basePagesCount) } for i := crcSize; i < len(buf); i += ptrSize { pageNo, _ := bin.GetUint32(buf[i:]) s.free = append(s.free, pageNo) } if s.deltaPagesCount > 0 { s.deltaPagesCount-- err = file.Truncate(off) if err != nil { return err } return file.Sync() } else { s.basePagesCount-- if s.basePagesCount >= s.frozenPageCount { err = s.baseFile.Truncate(off) if err != nil { return err } return file.Sync() } } return nil } func (s *FreeList) GetPageNumber() (pageNo uint32, err error) { if len(s.free) == 0 { if s.basePagesCount > 0 || s.deltaPagesCount > 0 { // якщо немає вільних сторінок у буфері, але є у freelist файлах - // завантажую сторінку із диску err = s.loadLastPage() if err != nil { return } } else { return } } if len(s.free) > 0 { 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:])) // } // fmt.Println(s.free) // 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 // } func (s *FreeList) Cached() []uint32 { return s.free } func (s *FreeList) Merge() (err error) { var ( off = int64(s.basePagesCount * s.pageSize) deltaSize = int64(s.deltaPagesCount * s.pageSize) ) if s.deltaPagesCount > 0 { var ( ret int64 copied int64 ) ret, err = s.baseFile.Seek(off, 0) if err != nil { return } if ret != off { return fmt.Errorf("Seek failed") } copied, err = io.Copy(s.baseFile, s.deltaFile) if err != nil { return } if copied != deltaSize { return fmt.Errorf("copy failed") } err = s.baseFile.Sync() if err != nil { return } err = s.deltaFile.Truncate(0) if err != nil { return } s.basePagesCount += s.deltaPagesCount s.deltaPagesCount = 0 } if s.basePagesCount < s.frozenPageCount { off = int64(s.basePagesCount * s.pageSize) err = s.baseFile.Truncate(off) if err != nil { return } } s.frozenPageCount = s.basePagesCount return nil }