]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Skip holes in file piece WriteTo
authorMatt Joiner <anacrolix@gmail.com>
Mon, 21 Jul 2025 12:06:19 +0000 (22:06 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Tue, 22 Jul 2025 13:17:42 +0000 (23:17 +1000)
Overkill optimization, saves about 10% reading from disk in test. Probably not worth it.

segments/segments_test.go
storage/file-piece.go
torrent.go

index 1620f77bd5cba9b0c09a164a3e822bc15de7678c..7335964908abb9e7b054d531b3086e2f697e94cc 100644 (file)
@@ -3,7 +3,7 @@ package segments
 import (
        "testing"
 
-       qt "github.com/go-quicktest/qt"
+       "github.com/go-quicktest/qt"
 )
 
 func LengthIterFromSlice(ls []Length) LengthIter {
@@ -86,6 +86,24 @@ func testLocater(t *testing.T, newLocater newLocater) {
                        {0, 1536},
                        {0, 667},
                })
+       checkContiguous(t, newLocater,
+               []Length{0, 2, 0, 2, 0}, // 128737588
+               Extent{1, 2},
+               1,
+               []Extent{
+                       {1, 1},
+                       {0, 0},
+                       {0, 1},
+               })
+       checkContiguous(t, newLocater,
+               []Length{2, 0, 2, 0}, // 128737588
+               Extent{1, 3},
+               0,
+               []Extent{
+                       {1, 1},
+                       {0, 0},
+                       {0, 2},
+               })
 }
 
 func TestScan(t *testing.T) {
@@ -97,3 +115,17 @@ func TestIndex(t *testing.T) {
                return NewIndex(li).Locate
        })
 }
+
+func TestIndexLocateIter(t *testing.T) {
+       testLocater(t, func(li LengthIter) Locater {
+               index := NewIndex(li)
+               return func(extent Extent, callback Callback) bool {
+                       for i, e := range index.LocateIter(extent) {
+                               if !callback(i, e) {
+                                       return false
+                               }
+                       }
+                       return true
+               }
+       })
+}
index 654ea155c9060c1de3cfadbd899991345d924e70..94fb9922f2904a4ad6ab47517bcc660109795596 100644 (file)
@@ -2,6 +2,7 @@ package storage
 
 import (
        "errors"
+       "expvar"
        "fmt"
        "io"
        "io/fs"
@@ -11,6 +12,7 @@ import (
 
        g "github.com/anacrolix/generics"
        "github.com/anacrolix/missinggo/v2/panicif"
+       "golang.org/x/sys/unix"
 
        "github.com/anacrolix/torrent/metainfo"
        "github.com/anacrolix/torrent/segments"
@@ -316,23 +318,69 @@ func (me *filePieceImpl) partFiles() bool {
        return me.t.partFiles()
 }
 
+type zeroReader struct{}
+
+func (me zeroReader) Read(p []byte) (n int, err error) {
+       clear(p)
+       return len(p), nil
+}
+
 func (me *filePieceImpl) WriteTo(w io.Writer) (n int64, err error) {
        for fileIndex, extent := range me.iterFileSegments() {
-               file := me.t.file(fileIndex)
-               var f *os.File
-               f, err = me.t.openFile(file)
+               var n1 int64
+               n1, err = me.writeFileTo(w, fileIndex, extent)
+               n += n1
                if err != nil {
                        return
                }
-               f.Seek(extent.Start, io.SeekStart)
+               panicif.NotEq(n1, extent.Length)
+       }
+       return
+}
+
+var (
+       packageExpvarMap = expvar.NewMap("torrentStorage")
+)
+
+func (me *filePieceImpl) writeFileTo(w io.Writer, fileIndex int, extent segments.Extent) (written int64, err error) {
+       if extent.Length == 0 {
+               return
+       }
+       file := me.t.file(fileIndex)
+       var f *os.File
+       f, err = me.t.openFile(file)
+       if err != nil {
+               return
+       }
+       defer f.Close()
+       panicif.GreaterThan(extent.End(), file.FileInfo.Length)
+       extentRemaining := extent.Length
+       var dataOffset int64
+       dataOffset, err = unix.Seek(int(f.Fd()), extent.Start, unix.SEEK_DATA)
+       if err == unix.ENXIO {
+               // File has no more data. Treat as short write like io.CopyN.
+               err = io.EOF
+               return
+       }
+       panicif.Err(err)
+       panicif.LessThan(dataOffset, extent.Start)
+       if dataOffset > extent.Start {
+               // Write zeroes until the end of the hole we're in.
                var n1 int64
-               n1, err = io.CopyN(w, f, extent.Length)
-               n += n1
-               f.Close()
+               n := min(dataOffset-extent.Start, extent.Length)
+               n1, err = io.CopyN(w, zeroReader{}, n)
+               packageExpvarMap.Add("bytesReadSkippedHole", n1)
+               written += n1
                if err != nil {
                        return
                }
+               panicif.NotEq(n1, n)
+               extentRemaining -= n1
        }
+       var n1 int64
+       n1, err = io.CopyN(w, f, extentRemaining)
+       packageExpvarMap.Add("bytesReadNotSkipped", n1)
+       written += n1
        return
 }
 
index e854c38a91debb2f00ff9a9fbbf788f4014e2286..1fa8ca784c1cff33e0b207f424874e79418928b3 100644 (file)
@@ -1250,8 +1250,8 @@ func (t *Torrent) hashPiece(piece pieceIndex) (
                differingPeers, err = t.hashPieceWithSpecificHash(piece, h)
                var sum [32]byte
                // What about the final piece in a torrent? From BEP 52: "The layer is chosen so that one
-               // hash covers piece length bytes.". Note that if a piece doesn't have a hash in piece
-               // layers it's because it's not larger than the piece length.
+               // hash covers piece length bytes". Note that if a piece doesn't have a hash in piece layers
+               // it's because it's not larger than the piece length.
                sumExactly(sum[:], func(b []byte) []byte {
                        return h.SumMinLength(b, int(t.info.PieceLength))
                })