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, } } } } }