github.com/grailbio/base@v0.0.11/morebufio/readerat.go (about) 1 package morebufio 2 3 import ( 4 "context" 5 "io" 6 "sync" 7 8 "github.com/grailbio/base/errors" 9 "github.com/grailbio/base/ioctx" 10 ) 11 12 type readerAt struct { 13 r ioctx.ReaderAt 14 // mu guards updates to all subsequent fields. It's not held during reads. 15 mu sync.Mutex 16 // seekerInUse is true while waiting for operations (like reads) on seeker. 17 seekerInUse bool 18 seeker *readSeeker 19 } 20 21 // NewReaderAt constructs a buffered ReaderAt. While ReaderAt allows arbitrary concurrent reads, 22 // this implementation has only one buffer (for simplicity), so reads will only be usefully buffered 23 // if reads are serial and generally contiguous (just like a plain ioctx.Reader). Concurrent reads 24 // will be "passed through" to the underlying ReaderAt, just without buffering. 25 func NewReaderAtSize(r ioctx.ReaderAt, size int) ioctx.ReaderAt { 26 return &readerAt{ 27 r: r, 28 seeker: NewReadSeekerSize(&readerAtSeeker{r: r}, size), 29 } 30 } 31 32 // ReadAt implements ioctx.ReaderAt. 33 func (r *readerAt) ReadAt(ctx context.Context, dst []byte, off int64) (int, error) { 34 acquired := r.tryAcquireSeeker() 35 if !acquired { 36 return r.r.ReadAt(ctx, dst, off) 37 } 38 defer r.releaseSeeker() 39 if _, err := r.seeker.Seek(ctx, off, io.SeekStart); err != nil { 40 return 0, errors.E(err, "seeking for ReadAt") 41 } 42 return r.seeker.Read(ctx, dst) 43 } 44 45 func (r *readerAt) tryAcquireSeeker() bool { 46 r.mu.Lock() 47 defer r.mu.Unlock() 48 if r.seekerInUse { 49 return false 50 } 51 r.seekerInUse = true 52 return true 53 } 54 55 func (r *readerAt) releaseSeeker() { 56 r.mu.Lock() 57 defer r.mu.Unlock() 58 if !r.seekerInUse { 59 panic("release of unacquired seeker") 60 } 61 r.seekerInUse = false 62 } 63 64 // readerAtSeeker is a simple ioctx.ReadSeeker that only supports seeking by io.SeekCurrent, 65 // which is all readSeeker requires. 66 type readerAtSeeker struct { 67 r ioctx.ReaderAt 68 pos int64 69 } 70 71 func (r *readerAtSeeker) Read(ctx context.Context, p []byte) (int, error) { 72 n, err := r.r.ReadAt(ctx, p, r.pos) 73 r.pos += int64(n) 74 return n, err 75 } 76 77 func (r *readerAtSeeker) Seek(ctx context.Context, request int64, whence int) (int64, error) { 78 if whence == io.SeekCurrent { 79 r.pos += request 80 return r.pos, nil 81 } 82 // Pretend the end position is zero. readSeeker requests this at initialization but we 83 // won't use it after that. 84 r.pos = 0 85 return r.pos, nil 86 }