From: Matt Joiner Date: Sat, 6 Jan 2018 12:15:41 +0000 (+1100) Subject: Readers obtained from File.NewReader should not readahead into other Files X-Git-Tag: v1.0.0~295 X-Git-Url: http://www.git.stargrave.org/?a=commitdiff_plain;h=87a033e0747ba8f4d7f1437ae8f18dddbd4692b5;p=btrtrc.git Readers obtained from File.NewReader should not readahead into other Files Fixes #221. Additionally Torrent.length is a pointer and isn't set until the info is available to avoid it defaulting to zero. --- diff --git a/file.go b/file.go index 8c5ac6d7..19729b4c 100644 --- a/file.go +++ b/file.go @@ -3,7 +3,6 @@ package torrent import ( "strings" - "github.com/anacrolix/missinggo" "github.com/anacrolix/torrent/metainfo" ) @@ -107,6 +106,13 @@ func (f *File) Cancel() { } func (f *File) NewReader() Reader { - tr := f.t.NewReader() - return fileReader{missinggo.NewSectionReadSeeker(tr, f.Offset(), f.Length()), tr} + tr := reader{ + mu: &f.t.cl.mu, + t: f.t, + readahead: 5 * 1024 * 1024, + offset: f.Offset(), + length: f.Length(), + } + f.t.addReader(&tr) + return &tr } diff --git a/file_reader.go b/file_reader.go deleted file mode 100644 index dde43620..00000000 --- a/file_reader.go +++ /dev/null @@ -1,18 +0,0 @@ -package torrent - -import ( - "io" - - "github.com/anacrolix/missinggo" -) - -type fileReaderInherited interface { - io.Closer - SetReadahead(int64) - SetResponsive() -} - -type fileReader struct { - missinggo.ReadSeekContexter - fileReaderInherited -} diff --git a/reader.go b/reader.go index 1be3b0ad..8b5ca81d 100644 --- a/reader.go +++ b/reader.go @@ -29,6 +29,9 @@ type pieceRange struct { type reader struct { t *Torrent responsive bool + // Adjust the read/seek window to handle Readers locked to File extents + // and the like. + offset, length int64 // Ensure operations that change the position are exclusive, like Read() // and Seek(). opMu sync.Mutex @@ -74,7 +77,7 @@ func (r *reader) readable(off int64) (ret bool) { if r.t.closed.IsSet() { return true } - req, ok := r.t.offsetRequest(off) + req, ok := r.t.offsetRequest(r.offset + off) if !ok { panic(off) } @@ -117,9 +120,14 @@ func (r *reader) waitReadable(off int64) { func (r *reader) piecesUncached() (ret pieceRange) { ra := r.readahead if ra < 1 { + // Needs to be at least 1, because [x, x) means we don't want + // anything. ra = 1 } - ret.begin, ret.end = r.t.byteRegionPieces(r.pos, ra) + if ra > r.length-r.pos { + ra = r.length - r.pos + } + ret.begin, ret.end = r.t.byteRegionPieces(r.offset+r.pos, ra) return } @@ -162,7 +170,7 @@ func (r *reader) ReadContext(ctx context.Context, b []byte) (n int, err error) { r.posChanged() r.mu.Unlock() } - if r.pos >= r.t.length { + if r.pos >= r.length { err = io.EOF } else if err == io.EOF { err = io.ErrUnexpectedEOF @@ -183,7 +191,7 @@ func (r *reader) waitAvailable(pos, wanted int64, ctxErr *error) (avail int64) { // Performs at most one successful read to torrent storage. func (r *reader) readOnceAt(b []byte, pos int64, ctxErr *error) (n int, err error) { - if pos >= r.t.length { + if pos >= r.length { err = io.EOF return } @@ -245,7 +253,7 @@ func (r *reader) Seek(off int64, whence int) (ret int64, err error) { case io.SeekCurrent: r.pos += off case io.SeekEnd: - r.pos = r.t.info.TotalLength() + off + r.pos = r.length + off default: err = errors.New("bad whence") } diff --git a/t.go b/t.go index f687cb59..93d8c3bf 100644 --- a/t.go +++ b/t.go @@ -36,6 +36,7 @@ func (t *Torrent) NewReader() Reader { mu: &t.cl.mu, t: t, readahead: 5 * 1024 * 1024, + length: *t.length, } t.addReader(&r) return &r @@ -119,10 +120,7 @@ func (t *Torrent) Name() string { // The completed length of all the torrent data, in all its files. This is // derived from the torrent info, when it is available. func (t *Torrent) Length() int64 { - if t.info == nil { - panic("not valid until info obtained") - } - return t.length + return *t.length } // Returns a run-time generated metainfo for the torrent that includes the diff --git a/torrent.go b/torrent.go index 3cb32a13..301e2d6d 100644 --- a/torrent.go +++ b/torrent.go @@ -58,7 +58,7 @@ type Torrent struct { chunkPool *sync.Pool // Total length of the torrent in bytes. Stored because it's not O(1) to // get this from the info dict. - length int64 + length *int64 // The storage to open when the info dict becomes available. storageOpener *storage.Client @@ -328,10 +328,11 @@ func (t *Torrent) setInfoBytes(b []byte) error { if err != nil { return fmt.Errorf("error opening torrent storage: %s", err) } - t.length = 0 + var l int64 for _, f := range t.info.UpvertedFiles() { - t.length += f.Length + l += f.Length } + t.length = &l t.metadataBytes = b t.metadataCompletedChunks = nil t.makePieces() @@ -631,13 +632,13 @@ func (t *Torrent) close() (err error) { } func (t *Torrent) requestOffset(r request) int64 { - return torrentRequestOffset(t.length, int64(t.usualPieceSize()), r) + return torrentRequestOffset(*t.length, int64(t.usualPieceSize()), r) } // Return the request that would include the given offset into the torrent // data. Returns !ok if there is no such request. func (t *Torrent) offsetRequest(off int64) (req request, ok bool) { - return torrentOffsetRequest(t.length, t.info.PieceLength, int64(t.chunkSize), off) + return torrentOffsetRequest(*t.length, t.info.PieceLength, int64(t.chunkSize), off) } func (t *Torrent) writeChunk(piece int, begin int64, data []byte) (err error) { @@ -681,7 +682,7 @@ type Peer struct { func (t *Torrent) pieceLength(piece int) pp.Integer { if piece == t.numPieces()-1 { - ret := pp.Integer(t.length % t.info.PieceLength) + ret := pp.Integer(*t.length % t.info.PieceLength) if ret != 0 { return ret } @@ -897,7 +898,7 @@ func (t *Torrent) updatePiecePriorities(begin, end int) { // Returns the range of pieces [begin, end) that contains the extent of bytes. func (t *Torrent) byteRegionPieces(off, size int64) (begin, end int) { - if off >= t.length { + if off >= *t.length { return } if off < 0 {