151 lines
3.2 KiB
Go
151 lines
3.2 KiB
Go
|
package vfs
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
|
||
|
lru "github.com/hashicorp/golang-lru/v2"
|
||
|
"github.com/royalcat/ctxio"
|
||
|
)
|
||
|
|
||
|
// TODO переделать кеш в демон
|
||
|
|
||
|
const blockSize int64 = 1024 * 16 // 16KB
|
||
|
const defaultBlockCount = 32768 // 512MB of total usage
|
||
|
|
||
|
type archiveFileIndex struct {
|
||
|
archive string
|
||
|
filename string
|
||
|
}
|
||
|
|
||
|
type blockIndex struct {
|
||
|
index archiveFileIndex
|
||
|
off int64
|
||
|
}
|
||
|
|
||
|
var blockCache *lru.Cache[blockIndex, []byte]
|
||
|
|
||
|
func ChangeBufferSize(blockCount int) {
|
||
|
blockCache.Resize(blockCount)
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
var err error
|
||
|
blockCache, err = lru.New[blockIndex, []byte](defaultBlockCount)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func newRandomReaderFromLinear(index archiveFileIndex, size int64, readerFactory archiveFileReaderFactory) *randomReaderFromLinear {
|
||
|
return &randomReaderFromLinear{
|
||
|
index: index,
|
||
|
size: size,
|
||
|
readerFactory: readerFactory,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type randomReaderFromLinear struct {
|
||
|
index archiveFileIndex
|
||
|
readerFactory archiveFileReaderFactory
|
||
|
reader ctxio.ReadCloser
|
||
|
readen int64
|
||
|
size int64
|
||
|
closed bool
|
||
|
}
|
||
|
|
||
|
var _ ctxio.ReaderAt = (*randomReaderFromLinear)(nil)
|
||
|
var _ ctxio.Closer = (*randomReaderFromLinear)(nil)
|
||
|
|
||
|
// ReadAt implements ctxio.ReaderAt.
|
||
|
func (a *randomReaderFromLinear) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
|
||
|
ctx, span := tracer.Start(ctx, "archive.RandomReader.ReadAt")
|
||
|
defer span.End()
|
||
|
|
||
|
if a.closed {
|
||
|
return 0, errors.New("reader is closed")
|
||
|
}
|
||
|
|
||
|
if off >= a.size {
|
||
|
return 0, ctxio.EOF
|
||
|
}
|
||
|
|
||
|
aligntOff := (off / blockSize) * blockSize
|
||
|
|
||
|
block, ok := blockCache.Get(blockIndex{index: a.index, off: aligntOff})
|
||
|
if ok {
|
||
|
n = copy(p, block[off-aligntOff:])
|
||
|
if len(block) < int(blockSize) {
|
||
|
err = ctxio.EOF
|
||
|
}
|
||
|
|
||
|
return n, err
|
||
|
}
|
||
|
|
||
|
span.AddEvent("cache miss, reading from file")
|
||
|
if err := a.readTo(ctx, aligntOff+blockSize); err != nil && err != ctxio.EOF {
|
||
|
return 0, err
|
||
|
}
|
||
|
|
||
|
block, ok = blockCache.Get(blockIndex{index: a.index, off: aligntOff})
|
||
|
if !ok {
|
||
|
// WTF this theoretically shouldn't happen under normal scenarios
|
||
|
return 0, errors.New("block not found or block cache under too much pressure, try to increase the cache size")
|
||
|
}
|
||
|
|
||
|
n = copy(p, block[off-aligntOff:])
|
||
|
if len(block) < int(blockSize) {
|
||
|
err = ctxio.EOF
|
||
|
}
|
||
|
return n, err
|
||
|
}
|
||
|
|
||
|
func (a *randomReaderFromLinear) readTo(ctx context.Context, targetOffset int64) (err error) {
|
||
|
if a.reader == nil || a.readen > targetOffset {
|
||
|
a.reader, err = a.readerFactory(context.TODO())
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
a.readen = 0
|
||
|
}
|
||
|
|
||
|
for off := a.readen; off < targetOffset; off += blockSize {
|
||
|
// TODO sync.Pool ?
|
||
|
buf := make([]byte, blockSize)
|
||
|
n, err := a.reader.Read(ctx, buf)
|
||
|
if err != nil && err != ctxio.EOF {
|
||
|
return err
|
||
|
}
|
||
|
a.readen += int64(n)
|
||
|
if int64(n) < blockSize {
|
||
|
buf = buf[:n]
|
||
|
}
|
||
|
|
||
|
blockCache.Add(blockIndex{index: a.index, off: off}, buf)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Close implements ctxio.Closer.
|
||
|
func (a *randomReaderFromLinear) Close(ctx context.Context) error {
|
||
|
if a.closed {
|
||
|
return nil
|
||
|
}
|
||
|
a.closed = true
|
||
|
|
||
|
var errs []error
|
||
|
|
||
|
if a.reader != nil {
|
||
|
errs = append(errs, a.reader.Close(ctx))
|
||
|
}
|
||
|
|
||
|
for _, block := range blockCache.Keys() {
|
||
|
if block.index == a.index {
|
||
|
blockCache.Remove(block)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return errors.Join(errs...)
|
||
|
}
|