2026-06-15 10:47:24 +00:00
|
|
|
package pagecache
|
2026-02-10 14:02:11 +00:00
|
|
|
|
|
|
|
|
import (
|
2026-06-15 10:47:24 +00:00
|
|
|
"errors"
|
2026-02-10 14:02:11 +00:00
|
|
|
"fmt"
|
2026-06-15 10:47:24 +00:00
|
|
|
"os"
|
|
|
|
|
"sync"
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-02-23 11:02:29 +00:00
|
|
|
"gordenko.dev/dima/qb"
|
2026-02-10 14:02:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type readResult struct {
|
|
|
|
|
Data []byte
|
|
|
|
|
Err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// INDEX PAGES
|
|
|
|
|
|
2026-06-15 10:47:24 +00:00
|
|
|
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{}
|
2026-02-10 14:02:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:47:24 +00:00
|
|
|
type Options struct {
|
|
|
|
|
File *os.File
|
|
|
|
|
PageSize int
|
|
|
|
|
VerifyPageCRC func([]byte) error
|
2026-02-23 19:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:47:24 +00:00
|
|
|
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),
|
2026-06-15 05:32:15 +03:00
|
|
|
}
|
2026-06-15 10:47:24 +00:00
|
|
|
go s.pageReader()
|
|
|
|
|
return s, nil
|
2026-02-23 19:37:05 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:47:24 +00:00
|
|
|
func (s *PageCache) FetchPage(pageNo uint32) ([]byte, error) {
|
2026-06-15 05:32:15 +03:00
|
|
|
s.mutex.Lock()
|
|
|
|
|
p, ok := s.pages[pageNo]
|
|
|
|
|
if ok {
|
|
|
|
|
p.ReferenceCount++
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
return p.Buf, nil
|
|
|
|
|
}
|
2026-06-10 06:18:45 +03:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
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()
|
2026-06-10 06:18:45 +03:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
select {
|
|
|
|
|
case s.readSignalCh <- struct{}{}:
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
}
|
2026-06-10 06:18:45 +03:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
result := <-resultCh
|
|
|
|
|
if result.Err == nil {
|
2026-06-15 10:47:24 +00:00
|
|
|
result.Err = s.verifyPageCRC(result.Data)
|
2026-06-15 05:32:15 +03:00
|
|
|
}
|
|
|
|
|
return result.Data, result.Err
|
2026-02-10 14:02:11 +00:00
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:47:24 +00:00
|
|
|
func (s *PageCache) 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-06-15 10:47:24 +00:00
|
|
|
func (s *PageCache) pageReader() {
|
2026-02-10 14:02:11 +00:00
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case <-s.readSignalCh:
|
|
|
|
|
s.readPages()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-15 10:47:24 +00:00
|
|
|
func (s *PageCache) readPages() {
|
2026-06-15 05:32:15 +03:00
|
|
|
s.mutex.Lock()
|
|
|
|
|
if len(s.pagesToRead) == 0 {
|
|
|
|
|
s.mutex.Unlock()
|
2026-02-10 14:02:11 +00:00
|
|
|
return
|
|
|
|
|
}
|
2026-06-15 05:32:15 +03:00
|
|
|
pagesToRead := s.pagesToRead
|
|
|
|
|
s.pagesToRead = nil
|
|
|
|
|
s.mutex.Unlock()
|
2026-02-10 14:02:11 +00:00
|
|
|
|
2026-06-15 05:32:15 +03:00
|
|
|
for _, pageNo := range pagesToRead {
|
2026-06-15 10:47:24 +00:00
|
|
|
buf := make([]byte, s.pageSize)
|
|
|
|
|
off := int(pageNo-1) * s.pageSize
|
2026-06-15 05:32:15 +03:00
|
|
|
n, err := s.file.ReadAt(buf, int64(off))
|
2026-06-15 10:47:24 +00:00
|
|
|
if n != s.pageSize {
|
|
|
|
|
err = fmt.Errorf("read %d instead of %d", n, s.pageSize)
|
2026-06-15 05:32:15 +03:00
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-10 14:02:11 +00:00
|
|
|
}
|
|
|
|
|
}
|