Files
qb/pagecache/pagecache.go
2026-06-15 10:47:24 +00:00

164 lines
3.1 KiB
Go

package pagecache
import (
"errors"
"fmt"
"os"
"sync"
"gordenko.dev/dima/qb"
)
type readResult struct {
Data []byte
Err error
}
// INDEX PAGES
type _page struct {
PageNo uint32
Buf []byte
ReferenceCount int
}
type PageCache struct {
mutex sync.Mutex
pageSize int
verifyPageCRC func([]byte) error
file *os.File
pages map[uint32]*_page
pageWaits map[uint32][]chan readResult
pagesToRead []uint32
readSignalCh chan struct{}
}
type Options struct {
File *os.File
PageSize int
VerifyPageCRC func([]byte) error
}
func New(opt Options) (*PageCache, error) {
if opt.File == nil {
return nil, errors.New("File option is required")
}
if opt.PageSize <= 0 {
return nil, errors.New("PageSize option is required")
}
if opt.VerifyPageCRC == nil {
return nil, errors.New("VerifyPageCRC option is required")
}
s := &PageCache{
file: opt.File,
pageSize: opt.PageSize,
verifyPageCRC: opt.VerifyPageCRC,
pages: make(map[uint32]*_page),
pageWaits: make(map[uint32][]chan readResult),
readSignalCh: make(chan struct{}, 1),
}
go s.pageReader()
return s, nil
}
func (s *PageCache) FetchPage(pageNo uint32) ([]byte, error) {
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.verifyPageCRC(result.Data)
}
return result.Data, result.Err
}
func (s *PageCache) ReleasePage(pageNo uint32) {
s.mutex.Lock()
defer s.mutex.Unlock()
p, ok := s.pages[pageNo]
if ok {
if p.ReferenceCount > 0 {
p.ReferenceCount--
return
} else {
qb.Abort(
qb.ReferenceCountBug,
fmt.Errorf("call releasePage on page %d with reference count = %d",
pageNo, p.ReferenceCount),
)
}
}
}
func (s *PageCache) pageReader() {
for {
select {
case <-s.readSignalCh:
s.readPages()
}
}
}
func (s *PageCache) readPages() {
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, s.pageSize)
off := int(pageNo-1) * s.pageSize
n, err := s.file.ReadAt(buf, int64(off))
if n != s.pageSize {
err = fmt.Errorf("read %d instead of %d", n, s.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{
PageNo: pageNo,
Buf: buf,
ReferenceCount: len(resultChannels),
}
s.mutex.Unlock()
for _, resultCh := range resultChannels {
resultCh <- readResult{
Data: buf,
}
}
}
}
}